@sentry/babel-plugin-component-annotate
Advanced tools
+563
-1006
@@ -1,1071 +0,628 @@ | ||
| 'use strict'; | ||
| Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: 'Module' } }); | ||
| Object.defineProperty(exports, '__esModule', { value: true }); | ||
| function _iterableToArrayLimit(arr, i) { | ||
| var _i = null == arr ? null : "undefined" != typeof Symbol && arr[Symbol.iterator] || arr["@@iterator"]; | ||
| if (null != _i) { | ||
| var _s, | ||
| _e, | ||
| _x, | ||
| _r, | ||
| _arr = [], | ||
| _n = !0, | ||
| _d = !1; | ||
| try { | ||
| if (_x = (_i = _i.call(arr)).next, 0 === i) { | ||
| if (Object(_i) !== _i) return; | ||
| _n = !1; | ||
| } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0); | ||
| } catch (err) { | ||
| _d = !0, _e = err; | ||
| } finally { | ||
| try { | ||
| if (!_n && null != _i.return && (_r = _i.return(), Object(_r) !== _r)) return; | ||
| } finally { | ||
| if (_d) throw _e; | ||
| } | ||
| } | ||
| return _arr; | ||
| } | ||
| } | ||
| function _typeof(obj) { | ||
| "@babel/helpers - typeof"; | ||
| return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { | ||
| return typeof obj; | ||
| } : function (obj) { | ||
| return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; | ||
| }, _typeof(obj); | ||
| } | ||
| function _slicedToArray(arr, i) { | ||
| return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); | ||
| } | ||
| function _arrayWithHoles(arr) { | ||
| if (Array.isArray(arr)) return arr; | ||
| } | ||
| function _unsupportedIterableToArray(o, minLen) { | ||
| if (!o) return; | ||
| if (typeof o === "string") return _arrayLikeToArray(o, minLen); | ||
| var n = Object.prototype.toString.call(o).slice(8, -1); | ||
| if (n === "Object" && o.constructor) n = o.constructor.name; | ||
| if (n === "Map" || n === "Set") return Array.from(o); | ||
| if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); | ||
| } | ||
| function _arrayLikeToArray(arr, len) { | ||
| if (len == null || len > arr.length) len = arr.length; | ||
| for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; | ||
| return arr2; | ||
| } | ||
| function _nonIterableRest() { | ||
| throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); | ||
| } | ||
| function _createForOfIteratorHelper(o, allowArrayLike) { | ||
| var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; | ||
| if (!it) { | ||
| if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { | ||
| if (it) o = it; | ||
| var i = 0; | ||
| var F = function () {}; | ||
| return { | ||
| s: F, | ||
| n: function () { | ||
| if (i >= o.length) return { | ||
| done: true | ||
| }; | ||
| return { | ||
| done: false, | ||
| value: o[i++] | ||
| }; | ||
| }, | ||
| e: function (e) { | ||
| throw e; | ||
| }, | ||
| f: F | ||
| }; | ||
| } | ||
| throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); | ||
| } | ||
| var normalCompletion = true, | ||
| didErr = false, | ||
| err; | ||
| return { | ||
| s: function () { | ||
| it = it.call(o); | ||
| }, | ||
| n: function () { | ||
| var step = it.next(); | ||
| normalCompletion = step.done; | ||
| return step; | ||
| }, | ||
| e: function (e) { | ||
| didErr = true; | ||
| err = e; | ||
| }, | ||
| f: function () { | ||
| try { | ||
| if (!normalCompletion && it.return != null) it.return(); | ||
| } finally { | ||
| if (didErr) throw err; | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| //#region src/constants.ts | ||
| /** | ||
| * MIT License | ||
| * | ||
| * Copyright (c) 2020 Engineering at FullStory | ||
| * | ||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| * of this software and associated documentation files (the "Software"), to deal | ||
| * in the Software without restriction, including without limitation the rights | ||
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| * copies of the Software, and to permit persons to whom the Software is | ||
| * furnished to do so, subject to the following conditions: | ||
| * | ||
| * The above copyright notice and this permission notice shall be included in all | ||
| * copies or substantial portions of the Software. | ||
| * | ||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| * SOFTWARE. | ||
| * | ||
| */ | ||
| * MIT License | ||
| * | ||
| * Copyright (c) 2020 Engineering at FullStory | ||
| * | ||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| * of this software and associated documentation files (the "Software"), to deal | ||
| * in the Software without restriction, including without limitation the rights | ||
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| * copies of the Software, and to permit persons to whom the Software is | ||
| * furnished to do so, subject to the following conditions: | ||
| * | ||
| * The above copyright notice and this permission notice shall be included in all | ||
| * copies or substantial portions of the Software. | ||
| * | ||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| * SOFTWARE. | ||
| * | ||
| */ | ||
| const KNOWN_INCOMPATIBLE_PLUGINS = ["react-native-testfairy", "@react-navigation"]; | ||
| const DEFAULT_IGNORED_ELEMENTS = [ | ||
| "a", | ||
| "abbr", | ||
| "address", | ||
| "area", | ||
| "article", | ||
| "aside", | ||
| "audio", | ||
| "b", | ||
| "base", | ||
| "bdi", | ||
| "bdo", | ||
| "blockquote", | ||
| "body", | ||
| "br", | ||
| "button", | ||
| "canvas", | ||
| "caption", | ||
| "cite", | ||
| "code", | ||
| "col", | ||
| "colgroup", | ||
| "data", | ||
| "datalist", | ||
| "dd", | ||
| "del", | ||
| "details", | ||
| "dfn", | ||
| "dialog", | ||
| "div", | ||
| "dl", | ||
| "dt", | ||
| "em", | ||
| "embed", | ||
| "fieldset", | ||
| "figure", | ||
| "footer", | ||
| "form", | ||
| "h1", | ||
| "h2", | ||
| "h3", | ||
| "h4", | ||
| "h5", | ||
| "h6", | ||
| "head", | ||
| "header", | ||
| "hgroup", | ||
| "hr", | ||
| "html", | ||
| "i", | ||
| "iframe", | ||
| "img", | ||
| "input", | ||
| "ins", | ||
| "kbd", | ||
| "keygen", | ||
| "label", | ||
| "legend", | ||
| "li", | ||
| "link", | ||
| "main", | ||
| "map", | ||
| "mark", | ||
| "menu", | ||
| "menuitem", | ||
| "meter", | ||
| "nav", | ||
| "noscript", | ||
| "object", | ||
| "ol", | ||
| "optgroup", | ||
| "option", | ||
| "output", | ||
| "p", | ||
| "param", | ||
| "pre", | ||
| "progress", | ||
| "q", | ||
| "rb", | ||
| "rp", | ||
| "rt", | ||
| "rtc", | ||
| "ruby", | ||
| "s", | ||
| "samp", | ||
| "script", | ||
| "section", | ||
| "select", | ||
| "small", | ||
| "source", | ||
| "span", | ||
| "strong", | ||
| "style", | ||
| "sub", | ||
| "summary", | ||
| "sup", | ||
| "table", | ||
| "tbody", | ||
| "td", | ||
| "template", | ||
| "textarea", | ||
| "tfoot", | ||
| "th", | ||
| "thead", | ||
| "time", | ||
| "title", | ||
| "tr", | ||
| "track", | ||
| "u", | ||
| "ul", | ||
| "var", | ||
| "video", | ||
| "wbr" | ||
| ]; | ||
| var KNOWN_INCOMPATIBLE_PLUGINS = [ | ||
| // This module might be causing an issue preventing clicks. For safety, we won't run on this module. | ||
| "react-native-testfairy", | ||
| // This module checks for unexpected property keys and throws an exception. | ||
| "@react-navigation"]; | ||
| var DEFAULT_IGNORED_ELEMENTS = ["a", "abbr", "address", "area", "article", "aside", "audio", "b", "base", "bdi", "bdo", "blockquote", "body", "br", "button", "canvas", "caption", "cite", "code", "col", "colgroup", "data", "datalist", "dd", "del", "details", "dfn", "dialog", "div", "dl", "dt", "em", "embed", "fieldset", "figure", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", "i", "iframe", "img", "input", "ins", "kbd", "keygen", "label", "legend", "li", "link", "main", "map", "mark", "menu", "menuitem", "meter", "nav", "noscript", "object", "ol", "optgroup", "option", "output", "p", "param", "pre", "progress", "q", "rb", "rp", "rt", "rtc", "ruby", "s", "samp", "script", "section", "select", "small", "source", "span", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track", "u", "ul", "var", "video", "wbr"]; | ||
| /** | ||
| * MIT License | ||
| * | ||
| * Copyright (c) 2020 Engineering at FullStory | ||
| * | ||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| * of this software and associated documentation files (the "Software"), to deal | ||
| * in the Software without restriction, including without limitation the rights | ||
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| * copies of the Software, and to permit persons to whom the Software is | ||
| * furnished to do so, subject to the following conditions: | ||
| * | ||
| * The above copyright notice and this permission notice shall be included in all | ||
| * copies or substantial portions of the Software. | ||
| * | ||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| * SOFTWARE. | ||
| * | ||
| */ | ||
| /** | ||
| * The following code is based on the FullStory Babel plugin, but has been modified to work | ||
| * with Sentry products: | ||
| * | ||
| * - Added `sentry` to data properties, i.e `data-sentry-component` | ||
| * - Converted to TypeScript | ||
| * - Code cleanups | ||
| * - Highly modified to inject the data attributes into the root HTML elements of a component. | ||
| */ | ||
| var REACT_NATIVE_ELEMENTS = ["Image", "Text", "View", "ScrollView", "TextInput", "TouchableOpacity", "TouchableHighlight", "TouchableWithoutFeedback", "FlatList", "SectionList", "ActivityIndicator", "Button", "Switch", "Modal", "SafeAreaView", "StatusBar", "KeyboardAvoidingView", "RefreshControl", "Picker", "Slider"]; | ||
| // Shared context object for all JSX processing functions | ||
| // We must export the plugin as default, otherwise the Babel loader will not be able to resolve it when configured using its string identifier | ||
| function experimentalComponentNameAnnotatePlugin(_ref) { | ||
| var t = _ref.types; | ||
| return { | ||
| visitor: { | ||
| Program: { | ||
| enter: function enter(path, state) { | ||
| var fragmentContext = collectFragmentContext$1(path); | ||
| state.sentryFragmentContext = fragmentContext; | ||
| } | ||
| }, | ||
| FunctionDeclaration: function FunctionDeclaration(path, state) { | ||
| if (!path.node.id || !path.node.id.name) { | ||
| return; | ||
| } | ||
| var context = createJSXProcessingContext$1(state, t, path.node.id.name); | ||
| functionBodyPushAttributes$1(context, path); | ||
| }, | ||
| ArrowFunctionExpression: function ArrowFunctionExpression(path, state) { | ||
| // We're expecting a `VariableDeclarator` like `const MyComponent =` | ||
| var parent = path.parent; | ||
| if (!parent || !("id" in parent) || !parent.id || !("name" in parent.id) || !parent.id.name) { | ||
| return; | ||
| } | ||
| var context = createJSXProcessingContext$1(state, t, parent.id.name); | ||
| functionBodyPushAttributes$1(context, path); | ||
| }, | ||
| ClassDeclaration: function ClassDeclaration(path, state) { | ||
| var _name$node; | ||
| var name = path.get("id"); | ||
| var properties = path.get("body").get("body"); | ||
| var render = properties.find(function (prop) { | ||
| return prop.isClassMethod() && prop.get("key").isIdentifier({ | ||
| name: "render" | ||
| }); | ||
| }); | ||
| if (!render || !render.traverse) { | ||
| return; | ||
| } | ||
| var context = createJSXProcessingContext$1(state, t, ((_name$node = name.node) === null || _name$node === void 0 ? void 0 : _name$node.name) || ""); | ||
| render.traverse({ | ||
| ReturnStatement: function ReturnStatement(returnStatement) { | ||
| var arg = returnStatement.get("argument"); | ||
| if (!arg.isJSXElement() && !arg.isJSXFragment()) { | ||
| return; | ||
| } | ||
| processJSX$1(context, arg); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/experimental.ts | ||
| const REACT_NATIVE_ELEMENTS = [ | ||
| "Image", | ||
| "Text", | ||
| "View", | ||
| "ScrollView", | ||
| "TextInput", | ||
| "TouchableOpacity", | ||
| "TouchableHighlight", | ||
| "TouchableWithoutFeedback", | ||
| "FlatList", | ||
| "SectionList", | ||
| "ActivityIndicator", | ||
| "Button", | ||
| "Switch", | ||
| "Modal", | ||
| "SafeAreaView", | ||
| "StatusBar", | ||
| "KeyboardAvoidingView", | ||
| "RefreshControl", | ||
| "Picker", | ||
| "Slider" | ||
| ]; | ||
| function experimentalComponentNameAnnotatePlugin({ types: t }) { | ||
| return { visitor: { | ||
| Program: { enter(path, state) { | ||
| state.sentryFragmentContext = collectFragmentContext$1(path); | ||
| } }, | ||
| FunctionDeclaration(path, state) { | ||
| if (!path.node.id || !path.node.id.name) return; | ||
| functionBodyPushAttributes$1(createJSXProcessingContext$1(state, t, path.node.id.name), path); | ||
| }, | ||
| ArrowFunctionExpression(path, state) { | ||
| const parent = path.parent; | ||
| if (!parent || !("id" in parent) || !parent.id || !("name" in parent.id) || !parent.id.name) return; | ||
| functionBodyPushAttributes$1(createJSXProcessingContext$1(state, t, parent.id.name), path); | ||
| }, | ||
| ClassDeclaration(path, state) { | ||
| const name = path.get("id"); | ||
| const render = path.get("body").get("body").find((prop) => { | ||
| return prop.isClassMethod() && prop.get("key").isIdentifier({ name: "render" }); | ||
| }); | ||
| if (!render || !render.traverse) return; | ||
| const context = createJSXProcessingContext$1(state, t, name.node?.name || ""); | ||
| render.traverse({ ReturnStatement(returnStatement) { | ||
| const arg = returnStatement.get("argument"); | ||
| if (!arg.isJSXElement() && !arg.isJSXFragment()) return; | ||
| processJSX$1(context, arg); | ||
| } }); | ||
| } | ||
| } }; | ||
| } | ||
| /** | ||
| * Checks if an element name represents an HTML element (as opposed to a React component). | ||
| * HTML elements include standard lowercase HTML tags and React Native elements. | ||
| */ | ||
| * Checks if an element name represents an HTML element (as opposed to a React component). | ||
| * HTML elements include standard lowercase HTML tags and React Native elements. | ||
| */ | ||
| function isHtmlElement(elementName) { | ||
| // Unknown elements are not HTML elements | ||
| if (elementName === UNKNOWN_ELEMENT_NAME$1) { | ||
| return false; | ||
| } | ||
| // Check for lowercase first letter (standard HTML elements) | ||
| if (elementName.length > 0 && elementName.charAt(0) === elementName.charAt(0).toLowerCase()) { | ||
| return true; | ||
| } | ||
| // React Native elements typically start with uppercase but are still "native" elements | ||
| // We consider them HTML-like elements for annotation purposes | ||
| if (REACT_NATIVE_ELEMENTS.includes(elementName)) { | ||
| return true; | ||
| } | ||
| // Otherwise, assume it's a React component (PascalCase) | ||
| return false; | ||
| if (elementName === UNKNOWN_ELEMENT_NAME$1) return false; | ||
| if (elementName.length > 0 && elementName.charAt(0) === elementName.charAt(0).toLowerCase()) return true; | ||
| if (REACT_NATIVE_ELEMENTS.includes(elementName)) return true; | ||
| return false; | ||
| } | ||
| /** | ||
| * Creates a JSX processing context from the plugin state | ||
| */ | ||
| * Creates a JSX processing context from the plugin state | ||
| */ | ||
| function createJSXProcessingContext$1(state, t, componentName) { | ||
| var _state$opts$ignoredCo; | ||
| return { | ||
| t: t, | ||
| componentName: componentName, | ||
| attributeName: attributeNamesFromState$1(state), | ||
| ignoredComponents: (_state$opts$ignoredCo = state.opts.ignoredComponents) !== null && _state$opts$ignoredCo !== void 0 ? _state$opts$ignoredCo : [], | ||
| fragmentContext: state.sentryFragmentContext | ||
| }; | ||
| return { | ||
| t, | ||
| componentName, | ||
| attributeName: attributeNamesFromState$1(state), | ||
| ignoredComponents: state.opts.ignoredComponents ?? [], | ||
| fragmentContext: state.sentryFragmentContext | ||
| }; | ||
| } | ||
| /** | ||
| * Processes the body of a function to add Sentry tracking attributes to JSX elements. | ||
| * Handles various function body structures including direct JSX returns, conditional expressions, | ||
| * and nested JSX elements. | ||
| */ | ||
| * Processes the body of a function to add Sentry tracking attributes to JSX elements. | ||
| * Handles various function body structures including direct JSX returns, conditional expressions, | ||
| * and nested JSX elements. | ||
| */ | ||
| function functionBodyPushAttributes$1(context, path) { | ||
| var jsxNode; | ||
| var functionBody = path.get("body").get("body"); | ||
| if (!("length" in functionBody) && functionBody.parent && (functionBody.parent.type === "JSXElement" || functionBody.parent.type === "JSXFragment")) { | ||
| var maybeJsxNode = functionBody.find(function (c) { | ||
| return c.type === "JSXElement" || c.type === "JSXFragment"; | ||
| }); | ||
| if (!maybeJsxNode) { | ||
| return; | ||
| } | ||
| jsxNode = maybeJsxNode; | ||
| } else { | ||
| var returnStatement = functionBody.find(function (c) { | ||
| return c.type === "ReturnStatement"; | ||
| }); | ||
| if (!returnStatement) { | ||
| return; | ||
| } | ||
| var arg = returnStatement.get("argument"); | ||
| if (!arg) { | ||
| return; | ||
| } | ||
| if (Array.isArray(arg)) { | ||
| return; | ||
| } | ||
| // Handle the case of a function body returning a ternary operation. | ||
| // `return (maybeTrue ? '' : (<SubComponent />))` | ||
| if (arg.isConditionalExpression()) { | ||
| var consequent = arg.get("consequent"); | ||
| if (consequent.isJSXFragment() || consequent.isJSXElement()) { | ||
| processJSX$1(context, consequent); | ||
| } | ||
| var alternate = arg.get("alternate"); | ||
| if (alternate.isJSXFragment() || alternate.isJSXElement()) { | ||
| processJSX$1(context, alternate); | ||
| } | ||
| return; | ||
| } | ||
| if (!arg.isJSXFragment() && !arg.isJSXElement()) { | ||
| return; | ||
| } | ||
| jsxNode = arg; | ||
| } | ||
| if (!jsxNode) { | ||
| return; | ||
| } | ||
| processJSX$1(context, jsxNode); | ||
| let jsxNode; | ||
| const functionBody = path.get("body").get("body"); | ||
| if (!("length" in functionBody) && functionBody.parent && (functionBody.parent.type === "JSXElement" || functionBody.parent.type === "JSXFragment")) { | ||
| const maybeJsxNode = functionBody.find((c) => { | ||
| return c.type === "JSXElement" || c.type === "JSXFragment"; | ||
| }); | ||
| if (!maybeJsxNode) return; | ||
| jsxNode = maybeJsxNode; | ||
| } else { | ||
| const returnStatement = functionBody.find((c) => { | ||
| return c.type === "ReturnStatement"; | ||
| }); | ||
| if (!returnStatement) return; | ||
| const arg = returnStatement.get("argument"); | ||
| if (!arg) return; | ||
| if (Array.isArray(arg)) return; | ||
| if (arg.isConditionalExpression()) { | ||
| const consequent = arg.get("consequent"); | ||
| if (consequent.isJSXFragment() || consequent.isJSXElement()) processJSX$1(context, consequent); | ||
| const alternate = arg.get("alternate"); | ||
| if (alternate.isJSXFragment() || alternate.isJSXElement()) processJSX$1(context, alternate); | ||
| return; | ||
| } | ||
| if (!arg.isJSXFragment() && !arg.isJSXElement()) return; | ||
| jsxNode = arg; | ||
| } | ||
| if (!jsxNode) return; | ||
| processJSX$1(context, jsxNode); | ||
| } | ||
| /** | ||
| * Recursively processes JSX elements to add Sentry tracking attributes. | ||
| * Handles both JSX elements and fragments, applying appropriate attributes | ||
| * based on configuration and component context. | ||
| */ | ||
| * Recursively processes JSX elements to add Sentry tracking attributes. | ||
| * Handles both JSX elements and fragments, applying appropriate attributes | ||
| * based on configuration and component context. | ||
| */ | ||
| function processJSX$1(context, jsxNode) { | ||
| if (!jsxNode) { | ||
| return; | ||
| } | ||
| // NOTE: I don't know of a case where `openingElement` would have more than one item, | ||
| // but it's safer to always iterate | ||
| var paths = jsxNode.get("openingElement"); | ||
| var openingElements = Array.isArray(paths) ? paths : [paths]; | ||
| var hasInjectedAttributes = openingElements.reduce(function (prev, openingElement) { | ||
| return prev || applyAttributes$1(context, openingElement, context.componentName); | ||
| }, false); | ||
| if (hasInjectedAttributes) { | ||
| return; | ||
| } | ||
| var children = jsxNode.get("children"); | ||
| // TODO: See why `Array.isArray` doesn't have correct behaviour here | ||
| if (children && !("length" in children)) { | ||
| // A single child was found, maybe a bit of static text | ||
| children = [children]; | ||
| } | ||
| children.forEach(function (child) { | ||
| // Happens for some node types like plain text | ||
| if (!child.node) { | ||
| return; | ||
| } | ||
| // If the current element is a fragment, children are still considered at root level | ||
| // Otherwise, children are not at root level | ||
| var openingElement = child.get("openingElement"); | ||
| // TODO: Improve this. We never expect to have multiple opening elements | ||
| // but if it's possible, this should work | ||
| if (Array.isArray(openingElement)) { | ||
| return; | ||
| } | ||
| processJSX$1(context, child); | ||
| }); | ||
| if (!jsxNode) return; | ||
| const paths = jsxNode.get("openingElement"); | ||
| if ((Array.isArray(paths) ? paths : [paths]).reduce((prev, openingElement) => prev || applyAttributes$1(context, openingElement, context.componentName), false)) return; | ||
| let children = jsxNode.get("children"); | ||
| if (children && !("length" in children)) children = [children]; | ||
| children.forEach((child) => { | ||
| if (!child.node) return; | ||
| const openingElement = child.get("openingElement"); | ||
| if (Array.isArray(openingElement)) return; | ||
| processJSX$1(context, child); | ||
| }); | ||
| } | ||
| /** | ||
| * Applies Sentry tracking attributes to a JSX opening element. | ||
| * Adds component name, element name, and source file attributes while | ||
| * respecting ignore lists and fragment detection. | ||
| */ | ||
| * Applies Sentry tracking attributes to a JSX opening element. | ||
| * Adds component name, element name, and source file attributes while | ||
| * respecting ignore lists and fragment detection. | ||
| */ | ||
| function applyAttributes$1(context, openingElement, componentName) { | ||
| var t = context.t, | ||
| componentAttributeName = context.attributeName, | ||
| ignoredComponents = context.ignoredComponents, | ||
| fragmentContext = context.fragmentContext; | ||
| // e.g., Raw JSX text like the `A` in `<h1>a</h1>` | ||
| if (!openingElement.node) { | ||
| return false; | ||
| } | ||
| // Check if this is a React fragment - if so, skip attribute addition entirely | ||
| var isFragment = isReactFragment$1(t, openingElement, fragmentContext); | ||
| if (isFragment) { | ||
| return false; | ||
| } | ||
| if (!openingElement.node.attributes) { | ||
| openingElement.node.attributes = []; | ||
| } | ||
| var elementName = getPathName$1(t, openingElement); | ||
| if (!isHtmlElement(elementName)) { | ||
| return false; | ||
| } | ||
| var isAnIgnoredComponent = ignoredComponents.some(function (ignoredComponent) { | ||
| return ignoredComponent === componentName || ignoredComponent === elementName; | ||
| }); | ||
| // Add a stable attribute for the component name (only for root elements) | ||
| if (!isAnIgnoredComponent && !hasAttributeWithName$1(openingElement, componentAttributeName)) { | ||
| if (componentAttributeName) { | ||
| openingElement.node.attributes.push(t.jSXAttribute(t.jSXIdentifier(componentAttributeName), t.stringLiteral(componentName))); | ||
| } | ||
| } | ||
| return true; | ||
| const { t, attributeName: componentAttributeName, ignoredComponents, fragmentContext } = context; | ||
| if (!openingElement.node) return false; | ||
| if (isReactFragment$1(t, openingElement, fragmentContext)) return false; | ||
| if (!openingElement.node.attributes) openingElement.node.attributes = []; | ||
| const elementName = getPathName$1(t, openingElement); | ||
| if (!isHtmlElement(elementName)) return false; | ||
| if (!ignoredComponents.some((ignoredComponent) => ignoredComponent === componentName || ignoredComponent === elementName) && !hasAttributeWithName$1(openingElement, componentAttributeName)) { | ||
| if (componentAttributeName) openingElement.node.attributes.push(t.jSXAttribute(t.jSXIdentifier(componentAttributeName), t.stringLiteral(componentName))); | ||
| } | ||
| return true; | ||
| } | ||
| function attributeNamesFromState$1(state) { | ||
| if (state.opts["native"]) { | ||
| return "dataSentryComponent"; | ||
| } | ||
| return "data-sentry-component"; | ||
| if (state.opts.native) return "dataSentryComponent"; | ||
| return "data-sentry-component"; | ||
| } | ||
| function collectFragmentContext$1(programPath) { | ||
| var fragmentAliases = new Set(); | ||
| var reactNamespaceAliases = new Set(["React"]); // Default React namespace | ||
| programPath.traverse({ | ||
| ImportDeclaration: function ImportDeclaration(importPath) { | ||
| var source = importPath.node.source.value; | ||
| // Handle React imports | ||
| if (source === "react" || source === "React") { | ||
| importPath.node.specifiers.forEach(function (spec) { | ||
| if (spec.type === "ImportSpecifier" && spec.imported.type === "Identifier") { | ||
| // Detect aliased React.Fragment imports (e.g., `Fragment as F`) | ||
| // so we can later identify <F> as a fragment in JSX. | ||
| if (spec.imported.name === "Fragment") { | ||
| fragmentAliases.add(spec.local.name); | ||
| } | ||
| } else if (spec.type === "ImportDefaultSpecifier" || spec.type === "ImportNamespaceSpecifier") { | ||
| // import React from 'react' -> React OR | ||
| // import * as React from 'react' -> React | ||
| reactNamespaceAliases.add(spec.local.name); | ||
| } | ||
| }); | ||
| } | ||
| }, | ||
| // Handle simple variable assignments only (avoid complex cases) | ||
| VariableDeclarator: function VariableDeclarator(varPath) { | ||
| if (varPath.node.init) { | ||
| var init = varPath.node.init; | ||
| // Handle identifier assignments: const MyFragment = Fragment | ||
| if (varPath.node.id.type === "Identifier") { | ||
| // Handle: const MyFragment = Fragment (only if Fragment is a known alias) | ||
| if (init.type === "Identifier" && fragmentAliases.has(init.name)) { | ||
| fragmentAliases.add(varPath.node.id.name); | ||
| } | ||
| // Handle: const MyFragment = React.Fragment (only for known React namespaces) | ||
| if (init.type === "MemberExpression" && init.object.type === "Identifier" && init.property.type === "Identifier" && init.property.name === "Fragment" && reactNamespaceAliases.has(init.object.name)) { | ||
| fragmentAliases.add(varPath.node.id.name); | ||
| } | ||
| } | ||
| // Handle destructuring assignments: const { Fragment } = React | ||
| if (varPath.node.id.type === "ObjectPattern") { | ||
| if (init.type === "Identifier" && reactNamespaceAliases.has(init.name)) { | ||
| var properties = varPath.node.id.properties; | ||
| var _iterator = _createForOfIteratorHelper(properties), | ||
| _step; | ||
| try { | ||
| for (_iterator.s(); !(_step = _iterator.n()).done;) { | ||
| var prop = _step.value; | ||
| if (prop.type === "ObjectProperty" && prop.key && prop.key.type === "Identifier" && prop.value && prop.value.type === "Identifier" && prop.key.name === "Fragment") { | ||
| fragmentAliases.add(prop.value.name); | ||
| } | ||
| } | ||
| } catch (err) { | ||
| _iterator.e(err); | ||
| } finally { | ||
| _iterator.f(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }); | ||
| return { | ||
| fragmentAliases: fragmentAliases, | ||
| reactNamespaceAliases: reactNamespaceAliases | ||
| }; | ||
| const fragmentAliases = /* @__PURE__ */ new Set(); | ||
| const reactNamespaceAliases = new Set(["React"]); | ||
| programPath.traverse({ | ||
| ImportDeclaration(importPath) { | ||
| const source = importPath.node.source.value; | ||
| if (source === "react" || source === "React") importPath.node.specifiers.forEach((spec) => { | ||
| if (spec.type === "ImportSpecifier" && spec.imported.type === "Identifier") { | ||
| if (spec.imported.name === "Fragment") fragmentAliases.add(spec.local.name); | ||
| } else if (spec.type === "ImportDefaultSpecifier" || spec.type === "ImportNamespaceSpecifier") reactNamespaceAliases.add(spec.local.name); | ||
| }); | ||
| }, | ||
| VariableDeclarator(varPath) { | ||
| if (varPath.node.init) { | ||
| const init = varPath.node.init; | ||
| if (varPath.node.id.type === "Identifier") { | ||
| if (init.type === "Identifier" && fragmentAliases.has(init.name)) fragmentAliases.add(varPath.node.id.name); | ||
| if (init.type === "MemberExpression" && init.object.type === "Identifier" && init.property.type === "Identifier" && init.property.name === "Fragment" && reactNamespaceAliases.has(init.object.name)) fragmentAliases.add(varPath.node.id.name); | ||
| } | ||
| if (varPath.node.id.type === "ObjectPattern") { | ||
| if (init.type === "Identifier" && reactNamespaceAliases.has(init.name)) { | ||
| const properties = varPath.node.id.properties; | ||
| for (const prop of properties) if (prop.type === "ObjectProperty" && prop.key && prop.key.type === "Identifier" && prop.value && prop.value.type === "Identifier" && prop.key.name === "Fragment") fragmentAliases.add(prop.value.name); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }); | ||
| return { | ||
| fragmentAliases, | ||
| reactNamespaceAliases | ||
| }; | ||
| } | ||
| function isReactFragment$1(t, openingElement, context) { | ||
| // Handle JSX fragments (<>) | ||
| if (openingElement.isJSXFragment()) { | ||
| return true; | ||
| } | ||
| var elementName = getPathName$1(t, openingElement); | ||
| // Direct fragment references | ||
| if (elementName === "Fragment" || elementName === "React.Fragment") { | ||
| return true; | ||
| } | ||
| // TODO: All these objects are typed as unknown, maybe an oversight in Babel types? | ||
| // Check if the element name is a known fragment alias | ||
| if (context && elementName && context.fragmentAliases.has(elementName)) { | ||
| return true; | ||
| } | ||
| // Handle JSXMemberExpression | ||
| if (openingElement.node && "name" in openingElement.node && openingElement.node.name && _typeof(openingElement.node.name) === "object" && "type" in openingElement.node.name && openingElement.node.name.type === "JSXMemberExpression") { | ||
| var nodeName = openingElement.node.name; | ||
| if (_typeof(nodeName) !== "object" || !nodeName) { | ||
| return false; | ||
| } | ||
| if ("object" in nodeName && "property" in nodeName) { | ||
| var nodeNameObject = nodeName.object; | ||
| var nodeNameProperty = nodeName.property; | ||
| if (_typeof(nodeNameObject) !== "object" || _typeof(nodeNameProperty) !== "object") { | ||
| return false; | ||
| } | ||
| if (!nodeNameObject || !nodeNameProperty) { | ||
| return false; | ||
| } | ||
| var objectName = "name" in nodeNameObject && nodeNameObject.name; | ||
| var propertyName = "name" in nodeNameProperty && nodeNameProperty.name; | ||
| // React.Fragment check | ||
| if (objectName === "React" && propertyName === "Fragment") { | ||
| return true; | ||
| } | ||
| // Enhanced checks using context | ||
| if (context) { | ||
| // Check React.Fragment pattern with known React namespaces | ||
| if (context.reactNamespaceAliases.has(objectName) && propertyName === "Fragment") { | ||
| return true; | ||
| } | ||
| // Check MyFragment.Fragment pattern | ||
| if (context.fragmentAliases.has(objectName) && propertyName === "Fragment") { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return false; | ||
| if (openingElement.isJSXFragment()) return true; | ||
| const elementName = getPathName$1(t, openingElement); | ||
| if (elementName === "Fragment" || elementName === "React.Fragment") return true; | ||
| if (context && elementName && context.fragmentAliases.has(elementName)) return true; | ||
| if (openingElement.node && "name" in openingElement.node && openingElement.node.name && typeof openingElement.node.name === "object" && "type" in openingElement.node.name && openingElement.node.name.type === "JSXMemberExpression") { | ||
| const nodeName = openingElement.node.name; | ||
| if (typeof nodeName !== "object" || !nodeName) return false; | ||
| if ("object" in nodeName && "property" in nodeName) { | ||
| const nodeNameObject = nodeName.object; | ||
| const nodeNameProperty = nodeName.property; | ||
| if (typeof nodeNameObject !== "object" || typeof nodeNameProperty !== "object") return false; | ||
| if (!nodeNameObject || !nodeNameProperty) return false; | ||
| const objectName = "name" in nodeNameObject && nodeNameObject.name; | ||
| const propertyName = "name" in nodeNameProperty && nodeNameProperty.name; | ||
| if (objectName === "React" && propertyName === "Fragment") return true; | ||
| if (context) { | ||
| if (context.reactNamespaceAliases.has(objectName) && propertyName === "Fragment") return true; | ||
| if (context.fragmentAliases.has(objectName) && propertyName === "Fragment") return true; | ||
| } | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| function hasAttributeWithName$1(openingElement, name) { | ||
| if (!name) { | ||
| return false; | ||
| } | ||
| return openingElement.node.attributes.some(function (node) { | ||
| if (node.type === "JSXAttribute") { | ||
| return node.name.name === name; | ||
| } | ||
| return false; | ||
| }); | ||
| if (!name) return false; | ||
| return openingElement.node.attributes.some((node) => { | ||
| if (node.type === "JSXAttribute") return node.name.name === name; | ||
| return false; | ||
| }); | ||
| } | ||
| function getPathName$1(t, path) { | ||
| if (!path.node) return UNKNOWN_ELEMENT_NAME$1; | ||
| if (!("name" in path.node)) { | ||
| return UNKNOWN_ELEMENT_NAME$1; | ||
| } | ||
| var name = path.node.name; | ||
| if (typeof name === "string") { | ||
| return name; | ||
| } | ||
| if (t.isIdentifier(name) || t.isJSXIdentifier(name)) { | ||
| return name.name; | ||
| } | ||
| if (t.isJSXNamespacedName(name)) { | ||
| return name.name.name; | ||
| } | ||
| // Handle JSX member expressions like Tab.Group | ||
| if (t.isJSXMemberExpression(name)) { | ||
| var objectName = getJSXMemberExpressionObjectName$1(t, name.object); | ||
| var propertyName = name.property.name; | ||
| return "".concat(objectName, ".").concat(propertyName); | ||
| } | ||
| return UNKNOWN_ELEMENT_NAME$1; | ||
| if (!path.node) return UNKNOWN_ELEMENT_NAME$1; | ||
| if (!("name" in path.node)) return UNKNOWN_ELEMENT_NAME$1; | ||
| const name = path.node.name; | ||
| if (typeof name === "string") return name; | ||
| if (t.isIdentifier(name) || t.isJSXIdentifier(name)) return name.name; | ||
| if (t.isJSXNamespacedName(name)) return name.name.name; | ||
| if (t.isJSXMemberExpression(name)) return `${getJSXMemberExpressionObjectName$1(t, name.object)}.${name.property.name}`; | ||
| return UNKNOWN_ELEMENT_NAME$1; | ||
| } | ||
| // Recursively handle nested member expressions (e.g. Components.UI.Header) | ||
| function getJSXMemberExpressionObjectName$1(t, object) { | ||
| if (t.isJSXIdentifier(object)) { | ||
| return object.name; | ||
| } | ||
| if (t.isJSXMemberExpression(object)) { | ||
| var objectName = getJSXMemberExpressionObjectName$1(t, object.object); | ||
| return "".concat(objectName, ".").concat(object.property.name); | ||
| } | ||
| return UNKNOWN_ELEMENT_NAME$1; | ||
| if (t.isJSXIdentifier(object)) return object.name; | ||
| if (t.isJSXMemberExpression(object)) return `${getJSXMemberExpressionObjectName$1(t, object.object)}.${object.property.name}`; | ||
| return UNKNOWN_ELEMENT_NAME$1; | ||
| } | ||
| var UNKNOWN_ELEMENT_NAME$1 = "unknown"; | ||
| const UNKNOWN_ELEMENT_NAME$1 = "unknown"; | ||
| var webComponentName = "data-sentry-component"; | ||
| var webElementName = "data-sentry-element"; | ||
| var webSourceFileName = "data-sentry-source-file"; | ||
| var nativeComponentName = "dataSentryComponent"; | ||
| var nativeElementName = "dataSentryElement"; | ||
| var nativeSourceFileName = "dataSentrySourceFile"; | ||
| // We must export the plugin as default, otherwise the Babel loader will not be able to resolve it when configured using its string identifier | ||
| function componentNameAnnotatePlugin(_ref) { | ||
| var t = _ref.types; | ||
| return { | ||
| visitor: { | ||
| Program: { | ||
| enter: function enter(path, state) { | ||
| var fragmentContext = collectFragmentContext(path); | ||
| state.sentryFragmentContext = fragmentContext; | ||
| } | ||
| }, | ||
| FunctionDeclaration: function FunctionDeclaration(path, state) { | ||
| if (!path.node.id || !path.node.id.name) { | ||
| return; | ||
| } | ||
| if (isKnownIncompatiblePluginFromState(state)) { | ||
| return; | ||
| } | ||
| var context = createJSXProcessingContext(state, t, path.node.id.name); | ||
| functionBodyPushAttributes(context, path); | ||
| }, | ||
| ArrowFunctionExpression: function ArrowFunctionExpression(path, state) { | ||
| // We're expecting a `VariableDeclarator` like `const MyComponent =` | ||
| var parent = path.parent; | ||
| if (!parent || !("id" in parent) || !parent.id || !("name" in parent.id) || !parent.id.name) { | ||
| return; | ||
| } | ||
| if (isKnownIncompatiblePluginFromState(state)) { | ||
| return; | ||
| } | ||
| var context = createJSXProcessingContext(state, t, parent.id.name); | ||
| functionBodyPushAttributes(context, path); | ||
| }, | ||
| ClassDeclaration: function ClassDeclaration(path, state) { | ||
| var _name$node; | ||
| var name = path.get("id"); | ||
| var properties = path.get("body").get("body"); | ||
| var render = properties.find(function (prop) { | ||
| return prop.isClassMethod() && prop.get("key").isIdentifier({ | ||
| name: "render" | ||
| }); | ||
| }); | ||
| if (!render || !render.traverse || isKnownIncompatiblePluginFromState(state)) { | ||
| return; | ||
| } | ||
| var context = createJSXProcessingContext(state, t, ((_name$node = name.node) === null || _name$node === void 0 ? void 0 : _name$node.name) || ""); | ||
| render.traverse({ | ||
| ReturnStatement: function ReturnStatement(returnStatement) { | ||
| var arg = returnStatement.get("argument"); | ||
| if (!arg.isJSXElement() && !arg.isJSXFragment()) { | ||
| return; | ||
| } | ||
| processJSX(context, arg); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/index.ts | ||
| const webComponentName = "data-sentry-component"; | ||
| const webElementName = "data-sentry-element"; | ||
| const webSourceFileName = "data-sentry-source-file"; | ||
| const nativeComponentName = "dataSentryComponent"; | ||
| const nativeElementName = "dataSentryElement"; | ||
| const nativeSourceFileName = "dataSentrySourceFile"; | ||
| function componentNameAnnotatePlugin({ types: t }) { | ||
| return { visitor: { | ||
| Program: { enter(path, state) { | ||
| state.sentryFragmentContext = collectFragmentContext(path); | ||
| } }, | ||
| FunctionDeclaration(path, state) { | ||
| if (!path.node.id || !path.node.id.name) return; | ||
| if (isKnownIncompatiblePluginFromState(state)) return; | ||
| functionBodyPushAttributes(createJSXProcessingContext(state, t, path.node.id.name), path); | ||
| }, | ||
| ArrowFunctionExpression(path, state) { | ||
| const parent = path.parent; | ||
| if (!parent || !("id" in parent) || !parent.id || !("name" in parent.id) || !parent.id.name) return; | ||
| if (isKnownIncompatiblePluginFromState(state)) return; | ||
| functionBodyPushAttributes(createJSXProcessingContext(state, t, parent.id.name), path); | ||
| }, | ||
| ClassDeclaration(path, state) { | ||
| const name = path.get("id"); | ||
| const render = path.get("body").get("body").find((prop) => { | ||
| return prop.isClassMethod() && prop.get("key").isIdentifier({ name: "render" }); | ||
| }); | ||
| if (!render || !render.traverse || isKnownIncompatiblePluginFromState(state)) return; | ||
| const context = createJSXProcessingContext(state, t, name.node?.name || ""); | ||
| render.traverse({ ReturnStatement(returnStatement) { | ||
| const arg = returnStatement.get("argument"); | ||
| if (!arg.isJSXElement() && !arg.isJSXFragment()) return; | ||
| processJSX(context, arg); | ||
| } }); | ||
| } | ||
| } }; | ||
| } | ||
| /** | ||
| * Creates a JSX processing context from the plugin state | ||
| */ | ||
| * Creates a JSX processing context from the plugin state | ||
| */ | ||
| function createJSXProcessingContext(state, t, componentName) { | ||
| var _state$opts$ignoredCo; | ||
| return { | ||
| annotateFragments: state.opts["annotate-fragments"] === true, | ||
| t: t, | ||
| componentName: componentName, | ||
| sourceFileName: sourceFileNameFromState(state), | ||
| attributeNames: attributeNamesFromState(state), | ||
| ignoredComponents: (_state$opts$ignoredCo = state.opts.ignoredComponents) !== null && _state$opts$ignoredCo !== void 0 ? _state$opts$ignoredCo : [], | ||
| fragmentContext: state.sentryFragmentContext | ||
| }; | ||
| return { | ||
| annotateFragments: state.opts["annotate-fragments"] === true, | ||
| t, | ||
| componentName, | ||
| sourceFileName: sourceFileNameFromState(state), | ||
| attributeNames: attributeNamesFromState(state), | ||
| ignoredComponents: state.opts.ignoredComponents ?? [], | ||
| fragmentContext: state.sentryFragmentContext | ||
| }; | ||
| } | ||
| /** | ||
| * Processes the body of a function to add Sentry tracking attributes to JSX elements. | ||
| * Handles various function body structures including direct JSX returns, conditional expressions, | ||
| * and nested JSX elements. | ||
| */ | ||
| * Processes the body of a function to add Sentry tracking attributes to JSX elements. | ||
| * Handles various function body structures including direct JSX returns, conditional expressions, | ||
| * and nested JSX elements. | ||
| */ | ||
| function functionBodyPushAttributes(context, path) { | ||
| var jsxNode; | ||
| var functionBody = path.get("body").get("body"); | ||
| if (!("length" in functionBody) && functionBody.parent && (functionBody.parent.type === "JSXElement" || functionBody.parent.type === "JSXFragment")) { | ||
| var maybeJsxNode = functionBody.find(function (c) { | ||
| return c.type === "JSXElement" || c.type === "JSXFragment"; | ||
| }); | ||
| if (!maybeJsxNode) { | ||
| return; | ||
| } | ||
| jsxNode = maybeJsxNode; | ||
| } else { | ||
| var returnStatement = functionBody.find(function (c) { | ||
| return c.type === "ReturnStatement"; | ||
| }); | ||
| if (!returnStatement) { | ||
| return; | ||
| } | ||
| var arg = returnStatement.get("argument"); | ||
| if (!arg) { | ||
| return; | ||
| } | ||
| if (Array.isArray(arg)) { | ||
| return; | ||
| } | ||
| // Handle the case of a function body returning a ternary operation. | ||
| // `return (maybeTrue ? '' : (<SubComponent />))` | ||
| if (arg.isConditionalExpression()) { | ||
| var consequent = arg.get("consequent"); | ||
| if (consequent.isJSXFragment() || consequent.isJSXElement()) { | ||
| processJSX(context, consequent); | ||
| } | ||
| var alternate = arg.get("alternate"); | ||
| if (alternate.isJSXFragment() || alternate.isJSXElement()) { | ||
| processJSX(context, alternate); | ||
| } | ||
| return; | ||
| } | ||
| if (!arg.isJSXFragment() && !arg.isJSXElement()) { | ||
| return; | ||
| } | ||
| jsxNode = arg; | ||
| } | ||
| if (!jsxNode) { | ||
| return; | ||
| } | ||
| processJSX(context, jsxNode); | ||
| let jsxNode; | ||
| const functionBody = path.get("body").get("body"); | ||
| if (!("length" in functionBody) && functionBody.parent && (functionBody.parent.type === "JSXElement" || functionBody.parent.type === "JSXFragment")) { | ||
| const maybeJsxNode = functionBody.find((c) => { | ||
| return c.type === "JSXElement" || c.type === "JSXFragment"; | ||
| }); | ||
| if (!maybeJsxNode) return; | ||
| jsxNode = maybeJsxNode; | ||
| } else { | ||
| const returnStatement = functionBody.find((c) => { | ||
| return c.type === "ReturnStatement"; | ||
| }); | ||
| if (!returnStatement) return; | ||
| const arg = returnStatement.get("argument"); | ||
| if (!arg) return; | ||
| if (Array.isArray(arg)) return; | ||
| if (arg.isConditionalExpression()) { | ||
| const consequent = arg.get("consequent"); | ||
| if (consequent.isJSXFragment() || consequent.isJSXElement()) processJSX(context, consequent); | ||
| const alternate = arg.get("alternate"); | ||
| if (alternate.isJSXFragment() || alternate.isJSXElement()) processJSX(context, alternate); | ||
| return; | ||
| } | ||
| if (!arg.isJSXFragment() && !arg.isJSXElement()) return; | ||
| jsxNode = arg; | ||
| } | ||
| if (!jsxNode) return; | ||
| processJSX(context, jsxNode); | ||
| } | ||
| /** | ||
| * Recursively processes JSX elements to add Sentry tracking attributes. | ||
| * Handles both JSX elements and fragments, applying appropriate attributes | ||
| * based on configuration and component context. | ||
| */ | ||
| * Recursively processes JSX elements to add Sentry tracking attributes. | ||
| * Handles both JSX elements and fragments, applying appropriate attributes | ||
| * based on configuration and component context. | ||
| */ | ||
| function processJSX(context, jsxNode, componentName) { | ||
| if (!jsxNode) { | ||
| return; | ||
| } | ||
| // Use provided componentName or fall back to context componentName | ||
| var currentComponentName = componentName !== null && componentName !== void 0 ? componentName : context.componentName; | ||
| // NOTE: I don't know of a case where `openingElement` would have more than one item, | ||
| // but it's safer to always iterate | ||
| var paths = jsxNode.get("openingElement"); | ||
| var openingElements = Array.isArray(paths) ? paths : [paths]; | ||
| openingElements.forEach(function (openingElement) { | ||
| applyAttributes(context, openingElement, currentComponentName); | ||
| }); | ||
| var children = jsxNode.get("children"); | ||
| // TODO: See why `Array.isArray` doesn't have correct behaviour here | ||
| if (children && !("length" in children)) { | ||
| // A single child was found, maybe a bit of static text | ||
| children = [children]; | ||
| } | ||
| var shouldSetComponentName = context.annotateFragments; | ||
| children.forEach(function (child) { | ||
| // Happens for some node types like plain text | ||
| if (!child.node) { | ||
| return; | ||
| } | ||
| // Children don't receive the data-component attribute so we pass null for componentName unless it's the first child of a Fragment with a node and `annotateFragments` is true | ||
| var openingElement = child.get("openingElement"); | ||
| // TODO: Improve this. We never expect to have multiple opening elements | ||
| // but if it's possible, this should work | ||
| if (Array.isArray(openingElement)) { | ||
| return; | ||
| } | ||
| if (shouldSetComponentName && openingElement && openingElement.node) { | ||
| shouldSetComponentName = false; | ||
| processJSX(context, child, currentComponentName); | ||
| } else { | ||
| processJSX(context, child, ""); | ||
| } | ||
| }); | ||
| if (!jsxNode) return; | ||
| const currentComponentName = componentName ?? context.componentName; | ||
| const paths = jsxNode.get("openingElement"); | ||
| (Array.isArray(paths) ? paths : [paths]).forEach((openingElement) => { | ||
| applyAttributes(context, openingElement, currentComponentName); | ||
| }); | ||
| let children = jsxNode.get("children"); | ||
| if (children && !("length" in children)) children = [children]; | ||
| let shouldSetComponentName = context.annotateFragments; | ||
| children.forEach((child) => { | ||
| if (!child.node) return; | ||
| const openingElement = child.get("openingElement"); | ||
| if (Array.isArray(openingElement)) return; | ||
| if (shouldSetComponentName && openingElement && openingElement.node) { | ||
| shouldSetComponentName = false; | ||
| processJSX(context, child, currentComponentName); | ||
| } else processJSX(context, child, ""); | ||
| }); | ||
| } | ||
| /** | ||
| * Applies Sentry tracking attributes to a JSX opening element. | ||
| * Adds component name, element name, and source file attributes while | ||
| * respecting ignore lists and fragment detection. | ||
| */ | ||
| * Applies Sentry tracking attributes to a JSX opening element. | ||
| * Adds component name, element name, and source file attributes while | ||
| * respecting ignore lists and fragment detection. | ||
| */ | ||
| function applyAttributes(context, openingElement, componentName) { | ||
| var t = context.t, | ||
| attributeNames = context.attributeNames, | ||
| ignoredComponents = context.ignoredComponents, | ||
| fragmentContext = context.fragmentContext, | ||
| sourceFileName = context.sourceFileName; | ||
| var _attributeNames = _slicedToArray(attributeNames, 3), | ||
| componentAttributeName = _attributeNames[0], | ||
| elementAttributeName = _attributeNames[1], | ||
| sourceFileAttributeName = _attributeNames[2]; | ||
| // e.g., Raw JSX text like the `A` in `<h1>a</h1>` | ||
| if (!openingElement.node) { | ||
| return; | ||
| } | ||
| // Check if this is a React fragment - if so, skip attribute addition entirely | ||
| var isFragment = isReactFragment(t, openingElement, fragmentContext); | ||
| if (isFragment) { | ||
| return; | ||
| } | ||
| if (!openingElement.node.attributes) openingElement.node.attributes = []; | ||
| var elementName = getPathName(t, openingElement); | ||
| var isAnIgnoredComponent = ignoredComponents.some(function (ignoredComponent) { | ||
| return ignoredComponent === componentName || ignoredComponent === elementName; | ||
| }); | ||
| // Add a stable attribute for the element name but only for non-DOM names | ||
| var isAnIgnoredElement = false; | ||
| if (!isAnIgnoredComponent && !hasAttributeWithName(openingElement, elementAttributeName)) { | ||
| if (DEFAULT_IGNORED_ELEMENTS.includes(elementName)) { | ||
| isAnIgnoredElement = true; | ||
| } else { | ||
| // Always add element attribute for non-ignored elements | ||
| if (elementAttributeName) { | ||
| openingElement.node.attributes.push(t.jSXAttribute(t.jSXIdentifier(elementAttributeName), t.stringLiteral(elementName))); | ||
| } | ||
| } | ||
| } | ||
| // Add a stable attribute for the component name (absent for non-root elements) | ||
| if (componentName && !isAnIgnoredComponent && !hasAttributeWithName(openingElement, componentAttributeName)) { | ||
| if (componentAttributeName) { | ||
| openingElement.node.attributes.push(t.jSXAttribute(t.jSXIdentifier(componentAttributeName), t.stringLiteral(componentName))); | ||
| } | ||
| } | ||
| // Add a stable attribute for the source file name | ||
| // Updated condition: add source file for elements that have either: | ||
| // 1. A component name (root elements), OR | ||
| // 2. An element name that's not ignored (child elements) | ||
| if (sourceFileName && !isAnIgnoredComponent && (componentName || !isAnIgnoredElement) && !hasAttributeWithName(openingElement, sourceFileAttributeName)) { | ||
| if (sourceFileAttributeName) { | ||
| openingElement.node.attributes.push(t.jSXAttribute(t.jSXIdentifier(sourceFileAttributeName), t.stringLiteral(sourceFileName))); | ||
| } | ||
| } | ||
| const { t, attributeNames, ignoredComponents, fragmentContext, sourceFileName } = context; | ||
| const [componentAttributeName, elementAttributeName, sourceFileAttributeName] = attributeNames; | ||
| if (!openingElement.node) return; | ||
| if (isReactFragment(t, openingElement, fragmentContext)) return; | ||
| if (!openingElement.node.attributes) openingElement.node.attributes = []; | ||
| const elementName = getPathName(t, openingElement); | ||
| const isAnIgnoredComponent = ignoredComponents.some((ignoredComponent) => ignoredComponent === componentName || ignoredComponent === elementName); | ||
| let isAnIgnoredElement = false; | ||
| if (!isAnIgnoredComponent && !hasAttributeWithName(openingElement, elementAttributeName)) { | ||
| if (DEFAULT_IGNORED_ELEMENTS.includes(elementName)) isAnIgnoredElement = true; | ||
| else if (elementAttributeName) openingElement.node.attributes.push(t.jSXAttribute(t.jSXIdentifier(elementAttributeName), t.stringLiteral(elementName))); | ||
| } | ||
| if (componentName && !isAnIgnoredComponent && !hasAttributeWithName(openingElement, componentAttributeName)) { | ||
| if (componentAttributeName) openingElement.node.attributes.push(t.jSXAttribute(t.jSXIdentifier(componentAttributeName), t.stringLiteral(componentName))); | ||
| } | ||
| if (sourceFileName && !isAnIgnoredComponent && (componentName || !isAnIgnoredElement) && !hasAttributeWithName(openingElement, sourceFileAttributeName)) { | ||
| if (sourceFileAttributeName) openingElement.node.attributes.push(t.jSXAttribute(t.jSXIdentifier(sourceFileAttributeName), t.stringLiteral(sourceFileName))); | ||
| } | ||
| } | ||
| function sourceFileNameFromState(state) { | ||
| var name = fullSourceFileNameFromState(state); | ||
| if (!name) { | ||
| return undefined; | ||
| } | ||
| if (name.indexOf("/") !== -1) { | ||
| return name.split("/").pop(); | ||
| } else if (name.indexOf("\\") !== -1) { | ||
| return name.split("\\").pop(); | ||
| } else { | ||
| return name; | ||
| } | ||
| const name = fullSourceFileNameFromState(state); | ||
| if (!name) return; | ||
| if (name.indexOf("/") !== -1) return name.split("/").pop(); | ||
| else if (name.indexOf("\\") !== -1) return name.split("\\").pop(); | ||
| else return name; | ||
| } | ||
| function fullSourceFileNameFromState(state) { | ||
| var _state$file$opts$pars; | ||
| // @ts-expect-error This type is incorrect in Babel, `sourceFileName` is the correct type | ||
| var name = (_state$file$opts$pars = state.file.opts.parserOpts) === null || _state$file$opts$pars === void 0 ? void 0 : _state$file$opts$pars.sourceFileName; | ||
| if (typeof name === "string") { | ||
| return name; | ||
| } | ||
| return null; | ||
| const name = state.file.opts.parserOpts?.sourceFileName; | ||
| if (typeof name === "string") return name; | ||
| return null; | ||
| } | ||
| function isKnownIncompatiblePluginFromState(state) { | ||
| var fullSourceFileName = fullSourceFileNameFromState(state); | ||
| if (!fullSourceFileName) { | ||
| return false; | ||
| } | ||
| return KNOWN_INCOMPATIBLE_PLUGINS.some(function (pluginName) { | ||
| if (fullSourceFileName.includes("/node_modules/".concat(pluginName, "/")) || fullSourceFileName.includes("\\node_modules\\".concat(pluginName, "\\"))) { | ||
| return true; | ||
| } | ||
| return false; | ||
| }); | ||
| const fullSourceFileName = fullSourceFileNameFromState(state); | ||
| if (!fullSourceFileName) return false; | ||
| return KNOWN_INCOMPATIBLE_PLUGINS.some((pluginName) => { | ||
| if (fullSourceFileName.includes(`/node_modules/${pluginName}/`) || fullSourceFileName.includes(`\\node_modules\\${pluginName}\\`)) return true; | ||
| return false; | ||
| }); | ||
| } | ||
| function attributeNamesFromState(state) { | ||
| if (state.opts["native"]) { | ||
| return [nativeComponentName, nativeElementName, nativeSourceFileName]; | ||
| } | ||
| return [webComponentName, webElementName, webSourceFileName]; | ||
| if (state.opts.native) return [ | ||
| nativeComponentName, | ||
| nativeElementName, | ||
| nativeSourceFileName | ||
| ]; | ||
| return [ | ||
| webComponentName, | ||
| webElementName, | ||
| webSourceFileName | ||
| ]; | ||
| } | ||
| function collectFragmentContext(programPath) { | ||
| var fragmentAliases = new Set(); | ||
| var reactNamespaceAliases = new Set(["React"]); // Default React namespace | ||
| programPath.traverse({ | ||
| ImportDeclaration: function ImportDeclaration(importPath) { | ||
| var source = importPath.node.source.value; | ||
| // Handle React imports | ||
| if (source === "react" || source === "React") { | ||
| importPath.node.specifiers.forEach(function (spec) { | ||
| if (spec.type === "ImportSpecifier" && spec.imported.type === "Identifier") { | ||
| // Detect aliased React.Fragment imports (e.g., `Fragment as F`) | ||
| // so we can later identify <F> as a fragment in JSX. | ||
| if (spec.imported.name === "Fragment") { | ||
| fragmentAliases.add(spec.local.name); | ||
| } | ||
| } else if (spec.type === "ImportDefaultSpecifier" || spec.type === "ImportNamespaceSpecifier") { | ||
| // import React from 'react' -> React OR | ||
| // import * as React from 'react' -> React | ||
| reactNamespaceAliases.add(spec.local.name); | ||
| } | ||
| }); | ||
| } | ||
| }, | ||
| // Handle simple variable assignments only (avoid complex cases) | ||
| VariableDeclarator: function VariableDeclarator(varPath) { | ||
| if (varPath.node.init) { | ||
| var init = varPath.node.init; | ||
| // Handle identifier assignments: const MyFragment = Fragment | ||
| if (varPath.node.id.type === "Identifier") { | ||
| // Handle: const MyFragment = Fragment (only if Fragment is a known alias) | ||
| if (init.type === "Identifier" && fragmentAliases.has(init.name)) { | ||
| fragmentAliases.add(varPath.node.id.name); | ||
| } | ||
| // Handle: const MyFragment = React.Fragment (only for known React namespaces) | ||
| if (init.type === "MemberExpression" && init.object.type === "Identifier" && init.property.type === "Identifier" && init.property.name === "Fragment" && reactNamespaceAliases.has(init.object.name)) { | ||
| fragmentAliases.add(varPath.node.id.name); | ||
| } | ||
| } | ||
| // Handle destructuring assignments: const { Fragment } = React | ||
| if (varPath.node.id.type === "ObjectPattern") { | ||
| if (init.type === "Identifier" && reactNamespaceAliases.has(init.name)) { | ||
| var properties = varPath.node.id.properties; | ||
| var _iterator = _createForOfIteratorHelper(properties), | ||
| _step; | ||
| try { | ||
| for (_iterator.s(); !(_step = _iterator.n()).done;) { | ||
| var prop = _step.value; | ||
| if (prop.type === "ObjectProperty" && prop.key && prop.key.type === "Identifier" && prop.value && prop.value.type === "Identifier" && prop.key.name === "Fragment") { | ||
| fragmentAliases.add(prop.value.name); | ||
| } | ||
| } | ||
| } catch (err) { | ||
| _iterator.e(err); | ||
| } finally { | ||
| _iterator.f(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }); | ||
| return { | ||
| fragmentAliases: fragmentAliases, | ||
| reactNamespaceAliases: reactNamespaceAliases | ||
| }; | ||
| const fragmentAliases = /* @__PURE__ */ new Set(); | ||
| const reactNamespaceAliases = new Set(["React"]); | ||
| programPath.traverse({ | ||
| ImportDeclaration(importPath) { | ||
| const source = importPath.node.source.value; | ||
| if (source === "react" || source === "React") importPath.node.specifiers.forEach((spec) => { | ||
| if (spec.type === "ImportSpecifier" && spec.imported.type === "Identifier") { | ||
| if (spec.imported.name === "Fragment") fragmentAliases.add(spec.local.name); | ||
| } else if (spec.type === "ImportDefaultSpecifier" || spec.type === "ImportNamespaceSpecifier") reactNamespaceAliases.add(spec.local.name); | ||
| }); | ||
| }, | ||
| VariableDeclarator(varPath) { | ||
| if (varPath.node.init) { | ||
| const init = varPath.node.init; | ||
| if (varPath.node.id.type === "Identifier") { | ||
| if (init.type === "Identifier" && fragmentAliases.has(init.name)) fragmentAliases.add(varPath.node.id.name); | ||
| if (init.type === "MemberExpression" && init.object.type === "Identifier" && init.property.type === "Identifier" && init.property.name === "Fragment" && reactNamespaceAliases.has(init.object.name)) fragmentAliases.add(varPath.node.id.name); | ||
| } | ||
| if (varPath.node.id.type === "ObjectPattern") { | ||
| if (init.type === "Identifier" && reactNamespaceAliases.has(init.name)) { | ||
| const properties = varPath.node.id.properties; | ||
| for (const prop of properties) if (prop.type === "ObjectProperty" && prop.key && prop.key.type === "Identifier" && prop.value && prop.value.type === "Identifier" && prop.key.name === "Fragment") fragmentAliases.add(prop.value.name); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }); | ||
| return { | ||
| fragmentAliases, | ||
| reactNamespaceAliases | ||
| }; | ||
| } | ||
| function isReactFragment(t, openingElement, context) { | ||
| // Handle JSX fragments (<>) | ||
| if (openingElement.isJSXFragment()) { | ||
| return true; | ||
| } | ||
| var elementName = getPathName(t, openingElement); | ||
| // Direct fragment references | ||
| if (elementName === "Fragment" || elementName === "React.Fragment") { | ||
| return true; | ||
| } | ||
| // TODO: All these objects are typed as unknown, maybe an oversight in Babel types? | ||
| // Check if the element name is a known fragment alias | ||
| if (context && elementName && context.fragmentAliases.has(elementName)) { | ||
| return true; | ||
| } | ||
| // Handle JSXMemberExpression | ||
| if (openingElement.node && "name" in openingElement.node && openingElement.node.name && _typeof(openingElement.node.name) === "object" && "type" in openingElement.node.name && openingElement.node.name.type === "JSXMemberExpression") { | ||
| var nodeName = openingElement.node.name; | ||
| if (_typeof(nodeName) !== "object" || !nodeName) { | ||
| return false; | ||
| } | ||
| if ("object" in nodeName && "property" in nodeName) { | ||
| var nodeNameObject = nodeName.object; | ||
| var nodeNameProperty = nodeName.property; | ||
| if (_typeof(nodeNameObject) !== "object" || _typeof(nodeNameProperty) !== "object") { | ||
| return false; | ||
| } | ||
| if (!nodeNameObject || !nodeNameProperty) { | ||
| return false; | ||
| } | ||
| var objectName = "name" in nodeNameObject && nodeNameObject.name; | ||
| var propertyName = "name" in nodeNameProperty && nodeNameProperty.name; | ||
| // React.Fragment check | ||
| if (objectName === "React" && propertyName === "Fragment") { | ||
| return true; | ||
| } | ||
| // Enhanced checks using context | ||
| if (context) { | ||
| // Check React.Fragment pattern with known React namespaces | ||
| if (context.reactNamespaceAliases.has(objectName) && propertyName === "Fragment") { | ||
| return true; | ||
| } | ||
| // Check MyFragment.Fragment pattern | ||
| if (context.fragmentAliases.has(objectName) && propertyName === "Fragment") { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return false; | ||
| if (openingElement.isJSXFragment()) return true; | ||
| const elementName = getPathName(t, openingElement); | ||
| if (elementName === "Fragment" || elementName === "React.Fragment") return true; | ||
| if (context && elementName && context.fragmentAliases.has(elementName)) return true; | ||
| if (openingElement.node && "name" in openingElement.node && openingElement.node.name && typeof openingElement.node.name === "object" && "type" in openingElement.node.name && openingElement.node.name.type === "JSXMemberExpression") { | ||
| const nodeName = openingElement.node.name; | ||
| if (typeof nodeName !== "object" || !nodeName) return false; | ||
| if ("object" in nodeName && "property" in nodeName) { | ||
| const nodeNameObject = nodeName.object; | ||
| const nodeNameProperty = nodeName.property; | ||
| if (typeof nodeNameObject !== "object" || typeof nodeNameProperty !== "object") return false; | ||
| if (!nodeNameObject || !nodeNameProperty) return false; | ||
| const objectName = "name" in nodeNameObject && nodeNameObject.name; | ||
| const propertyName = "name" in nodeNameProperty && nodeNameProperty.name; | ||
| if (objectName === "React" && propertyName === "Fragment") return true; | ||
| if (context) { | ||
| if (context.reactNamespaceAliases.has(objectName) && propertyName === "Fragment") return true; | ||
| if (context.fragmentAliases.has(objectName) && propertyName === "Fragment") return true; | ||
| } | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| function hasAttributeWithName(openingElement, name) { | ||
| if (!name) { | ||
| return false; | ||
| } | ||
| return openingElement.node.attributes.some(function (node) { | ||
| if (node.type === "JSXAttribute") { | ||
| return node.name.name === name; | ||
| } | ||
| return false; | ||
| }); | ||
| if (!name) return false; | ||
| return openingElement.node.attributes.some((node) => { | ||
| if (node.type === "JSXAttribute") return node.name.name === name; | ||
| return false; | ||
| }); | ||
| } | ||
| function getPathName(t, path) { | ||
| if (!path.node) return UNKNOWN_ELEMENT_NAME; | ||
| if (!("name" in path.node)) { | ||
| return UNKNOWN_ELEMENT_NAME; | ||
| } | ||
| var name = path.node.name; | ||
| if (typeof name === "string") { | ||
| return name; | ||
| } | ||
| if (t.isIdentifier(name) || t.isJSXIdentifier(name)) { | ||
| return name.name; | ||
| } | ||
| if (t.isJSXNamespacedName(name)) { | ||
| return name.name.name; | ||
| } | ||
| // Handle JSX member expressions like Tab.Group | ||
| if (t.isJSXMemberExpression(name)) { | ||
| var objectName = getJSXMemberExpressionObjectName(t, name.object); | ||
| var propertyName = name.property.name; | ||
| return "".concat(objectName, ".").concat(propertyName); | ||
| } | ||
| return UNKNOWN_ELEMENT_NAME; | ||
| if (!path.node) return UNKNOWN_ELEMENT_NAME; | ||
| if (!("name" in path.node)) return UNKNOWN_ELEMENT_NAME; | ||
| const name = path.node.name; | ||
| if (typeof name === "string") return name; | ||
| if (t.isIdentifier(name) || t.isJSXIdentifier(name)) return name.name; | ||
| if (t.isJSXNamespacedName(name)) return name.name.name; | ||
| if (t.isJSXMemberExpression(name)) return `${getJSXMemberExpressionObjectName(t, name.object)}.${name.property.name}`; | ||
| return UNKNOWN_ELEMENT_NAME; | ||
| } | ||
| // Recursively handle nested member expressions (e.g. Components.UI.Header) | ||
| function getJSXMemberExpressionObjectName(t, object) { | ||
| if (t.isJSXIdentifier(object)) { | ||
| return object.name; | ||
| } | ||
| if (t.isJSXMemberExpression(object)) { | ||
| var objectName = getJSXMemberExpressionObjectName(t, object.object); | ||
| return "".concat(objectName, ".").concat(object.property.name); | ||
| } | ||
| return UNKNOWN_ELEMENT_NAME; | ||
| if (t.isJSXIdentifier(object)) return object.name; | ||
| if (t.isJSXMemberExpression(object)) return `${getJSXMemberExpressionObjectName(t, object.object)}.${object.property.name}`; | ||
| return UNKNOWN_ELEMENT_NAME; | ||
| } | ||
| var UNKNOWN_ELEMENT_NAME = "unknown"; | ||
| const UNKNOWN_ELEMENT_NAME = "unknown"; | ||
| exports["default"] = componentNameAnnotatePlugin; | ||
| //#endregion | ||
| exports.default = componentNameAnnotatePlugin; | ||
| exports.experimentalComponentNameAnnotatePlugin = experimentalComponentNameAnnotatePlugin; | ||
| //# sourceMappingURL=index.js.map | ||
| //# sourceMappingURL=index.js.map |
+561
-1002
@@ -1,1066 +0,625 @@ | ||
| function _iterableToArrayLimit(arr, i) { | ||
| var _i = null == arr ? null : "undefined" != typeof Symbol && arr[Symbol.iterator] || arr["@@iterator"]; | ||
| if (null != _i) { | ||
| var _s, | ||
| _e, | ||
| _x, | ||
| _r, | ||
| _arr = [], | ||
| _n = !0, | ||
| _d = !1; | ||
| try { | ||
| if (_x = (_i = _i.call(arr)).next, 0 === i) { | ||
| if (Object(_i) !== _i) return; | ||
| _n = !1; | ||
| } else for (; !(_n = (_s = _x.call(_i)).done) && (_arr.push(_s.value), _arr.length !== i); _n = !0); | ||
| } catch (err) { | ||
| _d = !0, _e = err; | ||
| } finally { | ||
| try { | ||
| if (!_n && null != _i.return && (_r = _i.return(), Object(_r) !== _r)) return; | ||
| } finally { | ||
| if (_d) throw _e; | ||
| } | ||
| } | ||
| return _arr; | ||
| } | ||
| } | ||
| function _typeof(obj) { | ||
| "@babel/helpers - typeof"; | ||
| return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { | ||
| return typeof obj; | ||
| } : function (obj) { | ||
| return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; | ||
| }, _typeof(obj); | ||
| } | ||
| function _slicedToArray(arr, i) { | ||
| return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray(arr, i) || _nonIterableRest(); | ||
| } | ||
| function _arrayWithHoles(arr) { | ||
| if (Array.isArray(arr)) return arr; | ||
| } | ||
| function _unsupportedIterableToArray(o, minLen) { | ||
| if (!o) return; | ||
| if (typeof o === "string") return _arrayLikeToArray(o, minLen); | ||
| var n = Object.prototype.toString.call(o).slice(8, -1); | ||
| if (n === "Object" && o.constructor) n = o.constructor.name; | ||
| if (n === "Map" || n === "Set") return Array.from(o); | ||
| if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); | ||
| } | ||
| function _arrayLikeToArray(arr, len) { | ||
| if (len == null || len > arr.length) len = arr.length; | ||
| for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; | ||
| return arr2; | ||
| } | ||
| function _nonIterableRest() { | ||
| throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); | ||
| } | ||
| function _createForOfIteratorHelper(o, allowArrayLike) { | ||
| var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; | ||
| if (!it) { | ||
| if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { | ||
| if (it) o = it; | ||
| var i = 0; | ||
| var F = function () {}; | ||
| return { | ||
| s: F, | ||
| n: function () { | ||
| if (i >= o.length) return { | ||
| done: true | ||
| }; | ||
| return { | ||
| done: false, | ||
| value: o[i++] | ||
| }; | ||
| }, | ||
| e: function (e) { | ||
| throw e; | ||
| }, | ||
| f: F | ||
| }; | ||
| } | ||
| throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); | ||
| } | ||
| var normalCompletion = true, | ||
| didErr = false, | ||
| err; | ||
| return { | ||
| s: function () { | ||
| it = it.call(o); | ||
| }, | ||
| n: function () { | ||
| var step = it.next(); | ||
| normalCompletion = step.done; | ||
| return step; | ||
| }, | ||
| e: function (e) { | ||
| didErr = true; | ||
| err = e; | ||
| }, | ||
| f: function () { | ||
| try { | ||
| if (!normalCompletion && it.return != null) it.return(); | ||
| } finally { | ||
| if (didErr) throw err; | ||
| } | ||
| } | ||
| }; | ||
| } | ||
| //#region src/constants.ts | ||
| /** | ||
| * MIT License | ||
| * | ||
| * Copyright (c) 2020 Engineering at FullStory | ||
| * | ||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| * of this software and associated documentation files (the "Software"), to deal | ||
| * in the Software without restriction, including without limitation the rights | ||
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| * copies of the Software, and to permit persons to whom the Software is | ||
| * furnished to do so, subject to the following conditions: | ||
| * | ||
| * The above copyright notice and this permission notice shall be included in all | ||
| * copies or substantial portions of the Software. | ||
| * | ||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| * SOFTWARE. | ||
| * | ||
| */ | ||
| * MIT License | ||
| * | ||
| * Copyright (c) 2020 Engineering at FullStory | ||
| * | ||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| * of this software and associated documentation files (the "Software"), to deal | ||
| * in the Software without restriction, including without limitation the rights | ||
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| * copies of the Software, and to permit persons to whom the Software is | ||
| * furnished to do so, subject to the following conditions: | ||
| * | ||
| * The above copyright notice and this permission notice shall be included in all | ||
| * copies or substantial portions of the Software. | ||
| * | ||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| * SOFTWARE. | ||
| * | ||
| */ | ||
| const KNOWN_INCOMPATIBLE_PLUGINS = ["react-native-testfairy", "@react-navigation"]; | ||
| const DEFAULT_IGNORED_ELEMENTS = [ | ||
| "a", | ||
| "abbr", | ||
| "address", | ||
| "area", | ||
| "article", | ||
| "aside", | ||
| "audio", | ||
| "b", | ||
| "base", | ||
| "bdi", | ||
| "bdo", | ||
| "blockquote", | ||
| "body", | ||
| "br", | ||
| "button", | ||
| "canvas", | ||
| "caption", | ||
| "cite", | ||
| "code", | ||
| "col", | ||
| "colgroup", | ||
| "data", | ||
| "datalist", | ||
| "dd", | ||
| "del", | ||
| "details", | ||
| "dfn", | ||
| "dialog", | ||
| "div", | ||
| "dl", | ||
| "dt", | ||
| "em", | ||
| "embed", | ||
| "fieldset", | ||
| "figure", | ||
| "footer", | ||
| "form", | ||
| "h1", | ||
| "h2", | ||
| "h3", | ||
| "h4", | ||
| "h5", | ||
| "h6", | ||
| "head", | ||
| "header", | ||
| "hgroup", | ||
| "hr", | ||
| "html", | ||
| "i", | ||
| "iframe", | ||
| "img", | ||
| "input", | ||
| "ins", | ||
| "kbd", | ||
| "keygen", | ||
| "label", | ||
| "legend", | ||
| "li", | ||
| "link", | ||
| "main", | ||
| "map", | ||
| "mark", | ||
| "menu", | ||
| "menuitem", | ||
| "meter", | ||
| "nav", | ||
| "noscript", | ||
| "object", | ||
| "ol", | ||
| "optgroup", | ||
| "option", | ||
| "output", | ||
| "p", | ||
| "param", | ||
| "pre", | ||
| "progress", | ||
| "q", | ||
| "rb", | ||
| "rp", | ||
| "rt", | ||
| "rtc", | ||
| "ruby", | ||
| "s", | ||
| "samp", | ||
| "script", | ||
| "section", | ||
| "select", | ||
| "small", | ||
| "source", | ||
| "span", | ||
| "strong", | ||
| "style", | ||
| "sub", | ||
| "summary", | ||
| "sup", | ||
| "table", | ||
| "tbody", | ||
| "td", | ||
| "template", | ||
| "textarea", | ||
| "tfoot", | ||
| "th", | ||
| "thead", | ||
| "time", | ||
| "title", | ||
| "tr", | ||
| "track", | ||
| "u", | ||
| "ul", | ||
| "var", | ||
| "video", | ||
| "wbr" | ||
| ]; | ||
| var KNOWN_INCOMPATIBLE_PLUGINS = [ | ||
| // This module might be causing an issue preventing clicks. For safety, we won't run on this module. | ||
| "react-native-testfairy", | ||
| // This module checks for unexpected property keys and throws an exception. | ||
| "@react-navigation"]; | ||
| var DEFAULT_IGNORED_ELEMENTS = ["a", "abbr", "address", "area", "article", "aside", "audio", "b", "base", "bdi", "bdo", "blockquote", "body", "br", "button", "canvas", "caption", "cite", "code", "col", "colgroup", "data", "datalist", "dd", "del", "details", "dfn", "dialog", "div", "dl", "dt", "em", "embed", "fieldset", "figure", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "head", "header", "hgroup", "hr", "html", "i", "iframe", "img", "input", "ins", "kbd", "keygen", "label", "legend", "li", "link", "main", "map", "mark", "menu", "menuitem", "meter", "nav", "noscript", "object", "ol", "optgroup", "option", "output", "p", "param", "pre", "progress", "q", "rb", "rp", "rt", "rtc", "ruby", "s", "samp", "script", "section", "select", "small", "source", "span", "strong", "style", "sub", "summary", "sup", "table", "tbody", "td", "template", "textarea", "tfoot", "th", "thead", "time", "title", "tr", "track", "u", "ul", "var", "video", "wbr"]; | ||
| /** | ||
| * MIT License | ||
| * | ||
| * Copyright (c) 2020 Engineering at FullStory | ||
| * | ||
| * Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| * of this software and associated documentation files (the "Software"), to deal | ||
| * in the Software without restriction, including without limitation the rights | ||
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| * copies of the Software, and to permit persons to whom the Software is | ||
| * furnished to do so, subject to the following conditions: | ||
| * | ||
| * The above copyright notice and this permission notice shall be included in all | ||
| * copies or substantial portions of the Software. | ||
| * | ||
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| * SOFTWARE. | ||
| * | ||
| */ | ||
| /** | ||
| * The following code is based on the FullStory Babel plugin, but has been modified to work | ||
| * with Sentry products: | ||
| * | ||
| * - Added `sentry` to data properties, i.e `data-sentry-component` | ||
| * - Converted to TypeScript | ||
| * - Code cleanups | ||
| * - Highly modified to inject the data attributes into the root HTML elements of a component. | ||
| */ | ||
| var REACT_NATIVE_ELEMENTS = ["Image", "Text", "View", "ScrollView", "TextInput", "TouchableOpacity", "TouchableHighlight", "TouchableWithoutFeedback", "FlatList", "SectionList", "ActivityIndicator", "Button", "Switch", "Modal", "SafeAreaView", "StatusBar", "KeyboardAvoidingView", "RefreshControl", "Picker", "Slider"]; | ||
| // Shared context object for all JSX processing functions | ||
| // We must export the plugin as default, otherwise the Babel loader will not be able to resolve it when configured using its string identifier | ||
| function experimentalComponentNameAnnotatePlugin(_ref) { | ||
| var t = _ref.types; | ||
| return { | ||
| visitor: { | ||
| Program: { | ||
| enter: function enter(path, state) { | ||
| var fragmentContext = collectFragmentContext$1(path); | ||
| state.sentryFragmentContext = fragmentContext; | ||
| } | ||
| }, | ||
| FunctionDeclaration: function FunctionDeclaration(path, state) { | ||
| if (!path.node.id || !path.node.id.name) { | ||
| return; | ||
| } | ||
| var context = createJSXProcessingContext$1(state, t, path.node.id.name); | ||
| functionBodyPushAttributes$1(context, path); | ||
| }, | ||
| ArrowFunctionExpression: function ArrowFunctionExpression(path, state) { | ||
| // We're expecting a `VariableDeclarator` like `const MyComponent =` | ||
| var parent = path.parent; | ||
| if (!parent || !("id" in parent) || !parent.id || !("name" in parent.id) || !parent.id.name) { | ||
| return; | ||
| } | ||
| var context = createJSXProcessingContext$1(state, t, parent.id.name); | ||
| functionBodyPushAttributes$1(context, path); | ||
| }, | ||
| ClassDeclaration: function ClassDeclaration(path, state) { | ||
| var _name$node; | ||
| var name = path.get("id"); | ||
| var properties = path.get("body").get("body"); | ||
| var render = properties.find(function (prop) { | ||
| return prop.isClassMethod() && prop.get("key").isIdentifier({ | ||
| name: "render" | ||
| }); | ||
| }); | ||
| if (!render || !render.traverse) { | ||
| return; | ||
| } | ||
| var context = createJSXProcessingContext$1(state, t, ((_name$node = name.node) === null || _name$node === void 0 ? void 0 : _name$node.name) || ""); | ||
| render.traverse({ | ||
| ReturnStatement: function ReturnStatement(returnStatement) { | ||
| var arg = returnStatement.get("argument"); | ||
| if (!arg.isJSXElement() && !arg.isJSXFragment()) { | ||
| return; | ||
| } | ||
| processJSX$1(context, arg); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/experimental.ts | ||
| const REACT_NATIVE_ELEMENTS = [ | ||
| "Image", | ||
| "Text", | ||
| "View", | ||
| "ScrollView", | ||
| "TextInput", | ||
| "TouchableOpacity", | ||
| "TouchableHighlight", | ||
| "TouchableWithoutFeedback", | ||
| "FlatList", | ||
| "SectionList", | ||
| "ActivityIndicator", | ||
| "Button", | ||
| "Switch", | ||
| "Modal", | ||
| "SafeAreaView", | ||
| "StatusBar", | ||
| "KeyboardAvoidingView", | ||
| "RefreshControl", | ||
| "Picker", | ||
| "Slider" | ||
| ]; | ||
| function experimentalComponentNameAnnotatePlugin({ types: t }) { | ||
| return { visitor: { | ||
| Program: { enter(path, state) { | ||
| state.sentryFragmentContext = collectFragmentContext$1(path); | ||
| } }, | ||
| FunctionDeclaration(path, state) { | ||
| if (!path.node.id || !path.node.id.name) return; | ||
| functionBodyPushAttributes$1(createJSXProcessingContext$1(state, t, path.node.id.name), path); | ||
| }, | ||
| ArrowFunctionExpression(path, state) { | ||
| const parent = path.parent; | ||
| if (!parent || !("id" in parent) || !parent.id || !("name" in parent.id) || !parent.id.name) return; | ||
| functionBodyPushAttributes$1(createJSXProcessingContext$1(state, t, parent.id.name), path); | ||
| }, | ||
| ClassDeclaration(path, state) { | ||
| const name = path.get("id"); | ||
| const render = path.get("body").get("body").find((prop) => { | ||
| return prop.isClassMethod() && prop.get("key").isIdentifier({ name: "render" }); | ||
| }); | ||
| if (!render || !render.traverse) return; | ||
| const context = createJSXProcessingContext$1(state, t, name.node?.name || ""); | ||
| render.traverse({ ReturnStatement(returnStatement) { | ||
| const arg = returnStatement.get("argument"); | ||
| if (!arg.isJSXElement() && !arg.isJSXFragment()) return; | ||
| processJSX$1(context, arg); | ||
| } }); | ||
| } | ||
| } }; | ||
| } | ||
| /** | ||
| * Checks if an element name represents an HTML element (as opposed to a React component). | ||
| * HTML elements include standard lowercase HTML tags and React Native elements. | ||
| */ | ||
| * Checks if an element name represents an HTML element (as opposed to a React component). | ||
| * HTML elements include standard lowercase HTML tags and React Native elements. | ||
| */ | ||
| function isHtmlElement(elementName) { | ||
| // Unknown elements are not HTML elements | ||
| if (elementName === UNKNOWN_ELEMENT_NAME$1) { | ||
| return false; | ||
| } | ||
| // Check for lowercase first letter (standard HTML elements) | ||
| if (elementName.length > 0 && elementName.charAt(0) === elementName.charAt(0).toLowerCase()) { | ||
| return true; | ||
| } | ||
| // React Native elements typically start with uppercase but are still "native" elements | ||
| // We consider them HTML-like elements for annotation purposes | ||
| if (REACT_NATIVE_ELEMENTS.includes(elementName)) { | ||
| return true; | ||
| } | ||
| // Otherwise, assume it's a React component (PascalCase) | ||
| return false; | ||
| if (elementName === UNKNOWN_ELEMENT_NAME$1) return false; | ||
| if (elementName.length > 0 && elementName.charAt(0) === elementName.charAt(0).toLowerCase()) return true; | ||
| if (REACT_NATIVE_ELEMENTS.includes(elementName)) return true; | ||
| return false; | ||
| } | ||
| /** | ||
| * Creates a JSX processing context from the plugin state | ||
| */ | ||
| * Creates a JSX processing context from the plugin state | ||
| */ | ||
| function createJSXProcessingContext$1(state, t, componentName) { | ||
| var _state$opts$ignoredCo; | ||
| return { | ||
| t: t, | ||
| componentName: componentName, | ||
| attributeName: attributeNamesFromState$1(state), | ||
| ignoredComponents: (_state$opts$ignoredCo = state.opts.ignoredComponents) !== null && _state$opts$ignoredCo !== void 0 ? _state$opts$ignoredCo : [], | ||
| fragmentContext: state.sentryFragmentContext | ||
| }; | ||
| return { | ||
| t, | ||
| componentName, | ||
| attributeName: attributeNamesFromState$1(state), | ||
| ignoredComponents: state.opts.ignoredComponents ?? [], | ||
| fragmentContext: state.sentryFragmentContext | ||
| }; | ||
| } | ||
| /** | ||
| * Processes the body of a function to add Sentry tracking attributes to JSX elements. | ||
| * Handles various function body structures including direct JSX returns, conditional expressions, | ||
| * and nested JSX elements. | ||
| */ | ||
| * Processes the body of a function to add Sentry tracking attributes to JSX elements. | ||
| * Handles various function body structures including direct JSX returns, conditional expressions, | ||
| * and nested JSX elements. | ||
| */ | ||
| function functionBodyPushAttributes$1(context, path) { | ||
| var jsxNode; | ||
| var functionBody = path.get("body").get("body"); | ||
| if (!("length" in functionBody) && functionBody.parent && (functionBody.parent.type === "JSXElement" || functionBody.parent.type === "JSXFragment")) { | ||
| var maybeJsxNode = functionBody.find(function (c) { | ||
| return c.type === "JSXElement" || c.type === "JSXFragment"; | ||
| }); | ||
| if (!maybeJsxNode) { | ||
| return; | ||
| } | ||
| jsxNode = maybeJsxNode; | ||
| } else { | ||
| var returnStatement = functionBody.find(function (c) { | ||
| return c.type === "ReturnStatement"; | ||
| }); | ||
| if (!returnStatement) { | ||
| return; | ||
| } | ||
| var arg = returnStatement.get("argument"); | ||
| if (!arg) { | ||
| return; | ||
| } | ||
| if (Array.isArray(arg)) { | ||
| return; | ||
| } | ||
| // Handle the case of a function body returning a ternary operation. | ||
| // `return (maybeTrue ? '' : (<SubComponent />))` | ||
| if (arg.isConditionalExpression()) { | ||
| var consequent = arg.get("consequent"); | ||
| if (consequent.isJSXFragment() || consequent.isJSXElement()) { | ||
| processJSX$1(context, consequent); | ||
| } | ||
| var alternate = arg.get("alternate"); | ||
| if (alternate.isJSXFragment() || alternate.isJSXElement()) { | ||
| processJSX$1(context, alternate); | ||
| } | ||
| return; | ||
| } | ||
| if (!arg.isJSXFragment() && !arg.isJSXElement()) { | ||
| return; | ||
| } | ||
| jsxNode = arg; | ||
| } | ||
| if (!jsxNode) { | ||
| return; | ||
| } | ||
| processJSX$1(context, jsxNode); | ||
| let jsxNode; | ||
| const functionBody = path.get("body").get("body"); | ||
| if (!("length" in functionBody) && functionBody.parent && (functionBody.parent.type === "JSXElement" || functionBody.parent.type === "JSXFragment")) { | ||
| const maybeJsxNode = functionBody.find((c) => { | ||
| return c.type === "JSXElement" || c.type === "JSXFragment"; | ||
| }); | ||
| if (!maybeJsxNode) return; | ||
| jsxNode = maybeJsxNode; | ||
| } else { | ||
| const returnStatement = functionBody.find((c) => { | ||
| return c.type === "ReturnStatement"; | ||
| }); | ||
| if (!returnStatement) return; | ||
| const arg = returnStatement.get("argument"); | ||
| if (!arg) return; | ||
| if (Array.isArray(arg)) return; | ||
| if (arg.isConditionalExpression()) { | ||
| const consequent = arg.get("consequent"); | ||
| if (consequent.isJSXFragment() || consequent.isJSXElement()) processJSX$1(context, consequent); | ||
| const alternate = arg.get("alternate"); | ||
| if (alternate.isJSXFragment() || alternate.isJSXElement()) processJSX$1(context, alternate); | ||
| return; | ||
| } | ||
| if (!arg.isJSXFragment() && !arg.isJSXElement()) return; | ||
| jsxNode = arg; | ||
| } | ||
| if (!jsxNode) return; | ||
| processJSX$1(context, jsxNode); | ||
| } | ||
| /** | ||
| * Recursively processes JSX elements to add Sentry tracking attributes. | ||
| * Handles both JSX elements and fragments, applying appropriate attributes | ||
| * based on configuration and component context. | ||
| */ | ||
| * Recursively processes JSX elements to add Sentry tracking attributes. | ||
| * Handles both JSX elements and fragments, applying appropriate attributes | ||
| * based on configuration and component context. | ||
| */ | ||
| function processJSX$1(context, jsxNode) { | ||
| if (!jsxNode) { | ||
| return; | ||
| } | ||
| // NOTE: I don't know of a case where `openingElement` would have more than one item, | ||
| // but it's safer to always iterate | ||
| var paths = jsxNode.get("openingElement"); | ||
| var openingElements = Array.isArray(paths) ? paths : [paths]; | ||
| var hasInjectedAttributes = openingElements.reduce(function (prev, openingElement) { | ||
| return prev || applyAttributes$1(context, openingElement, context.componentName); | ||
| }, false); | ||
| if (hasInjectedAttributes) { | ||
| return; | ||
| } | ||
| var children = jsxNode.get("children"); | ||
| // TODO: See why `Array.isArray` doesn't have correct behaviour here | ||
| if (children && !("length" in children)) { | ||
| // A single child was found, maybe a bit of static text | ||
| children = [children]; | ||
| } | ||
| children.forEach(function (child) { | ||
| // Happens for some node types like plain text | ||
| if (!child.node) { | ||
| return; | ||
| } | ||
| // If the current element is a fragment, children are still considered at root level | ||
| // Otherwise, children are not at root level | ||
| var openingElement = child.get("openingElement"); | ||
| // TODO: Improve this. We never expect to have multiple opening elements | ||
| // but if it's possible, this should work | ||
| if (Array.isArray(openingElement)) { | ||
| return; | ||
| } | ||
| processJSX$1(context, child); | ||
| }); | ||
| if (!jsxNode) return; | ||
| const paths = jsxNode.get("openingElement"); | ||
| if ((Array.isArray(paths) ? paths : [paths]).reduce((prev, openingElement) => prev || applyAttributes$1(context, openingElement, context.componentName), false)) return; | ||
| let children = jsxNode.get("children"); | ||
| if (children && !("length" in children)) children = [children]; | ||
| children.forEach((child) => { | ||
| if (!child.node) return; | ||
| const openingElement = child.get("openingElement"); | ||
| if (Array.isArray(openingElement)) return; | ||
| processJSX$1(context, child); | ||
| }); | ||
| } | ||
| /** | ||
| * Applies Sentry tracking attributes to a JSX opening element. | ||
| * Adds component name, element name, and source file attributes while | ||
| * respecting ignore lists and fragment detection. | ||
| */ | ||
| * Applies Sentry tracking attributes to a JSX opening element. | ||
| * Adds component name, element name, and source file attributes while | ||
| * respecting ignore lists and fragment detection. | ||
| */ | ||
| function applyAttributes$1(context, openingElement, componentName) { | ||
| var t = context.t, | ||
| componentAttributeName = context.attributeName, | ||
| ignoredComponents = context.ignoredComponents, | ||
| fragmentContext = context.fragmentContext; | ||
| // e.g., Raw JSX text like the `A` in `<h1>a</h1>` | ||
| if (!openingElement.node) { | ||
| return false; | ||
| } | ||
| // Check if this is a React fragment - if so, skip attribute addition entirely | ||
| var isFragment = isReactFragment$1(t, openingElement, fragmentContext); | ||
| if (isFragment) { | ||
| return false; | ||
| } | ||
| if (!openingElement.node.attributes) { | ||
| openingElement.node.attributes = []; | ||
| } | ||
| var elementName = getPathName$1(t, openingElement); | ||
| if (!isHtmlElement(elementName)) { | ||
| return false; | ||
| } | ||
| var isAnIgnoredComponent = ignoredComponents.some(function (ignoredComponent) { | ||
| return ignoredComponent === componentName || ignoredComponent === elementName; | ||
| }); | ||
| // Add a stable attribute for the component name (only for root elements) | ||
| if (!isAnIgnoredComponent && !hasAttributeWithName$1(openingElement, componentAttributeName)) { | ||
| if (componentAttributeName) { | ||
| openingElement.node.attributes.push(t.jSXAttribute(t.jSXIdentifier(componentAttributeName), t.stringLiteral(componentName))); | ||
| } | ||
| } | ||
| return true; | ||
| const { t, attributeName: componentAttributeName, ignoredComponents, fragmentContext } = context; | ||
| if (!openingElement.node) return false; | ||
| if (isReactFragment$1(t, openingElement, fragmentContext)) return false; | ||
| if (!openingElement.node.attributes) openingElement.node.attributes = []; | ||
| const elementName = getPathName$1(t, openingElement); | ||
| if (!isHtmlElement(elementName)) return false; | ||
| if (!ignoredComponents.some((ignoredComponent) => ignoredComponent === componentName || ignoredComponent === elementName) && !hasAttributeWithName$1(openingElement, componentAttributeName)) { | ||
| if (componentAttributeName) openingElement.node.attributes.push(t.jSXAttribute(t.jSXIdentifier(componentAttributeName), t.stringLiteral(componentName))); | ||
| } | ||
| return true; | ||
| } | ||
| function attributeNamesFromState$1(state) { | ||
| if (state.opts["native"]) { | ||
| return "dataSentryComponent"; | ||
| } | ||
| return "data-sentry-component"; | ||
| if (state.opts.native) return "dataSentryComponent"; | ||
| return "data-sentry-component"; | ||
| } | ||
| function collectFragmentContext$1(programPath) { | ||
| var fragmentAliases = new Set(); | ||
| var reactNamespaceAliases = new Set(["React"]); // Default React namespace | ||
| programPath.traverse({ | ||
| ImportDeclaration: function ImportDeclaration(importPath) { | ||
| var source = importPath.node.source.value; | ||
| // Handle React imports | ||
| if (source === "react" || source === "React") { | ||
| importPath.node.specifiers.forEach(function (spec) { | ||
| if (spec.type === "ImportSpecifier" && spec.imported.type === "Identifier") { | ||
| // Detect aliased React.Fragment imports (e.g., `Fragment as F`) | ||
| // so we can later identify <F> as a fragment in JSX. | ||
| if (spec.imported.name === "Fragment") { | ||
| fragmentAliases.add(spec.local.name); | ||
| } | ||
| } else if (spec.type === "ImportDefaultSpecifier" || spec.type === "ImportNamespaceSpecifier") { | ||
| // import React from 'react' -> React OR | ||
| // import * as React from 'react' -> React | ||
| reactNamespaceAliases.add(spec.local.name); | ||
| } | ||
| }); | ||
| } | ||
| }, | ||
| // Handle simple variable assignments only (avoid complex cases) | ||
| VariableDeclarator: function VariableDeclarator(varPath) { | ||
| if (varPath.node.init) { | ||
| var init = varPath.node.init; | ||
| // Handle identifier assignments: const MyFragment = Fragment | ||
| if (varPath.node.id.type === "Identifier") { | ||
| // Handle: const MyFragment = Fragment (only if Fragment is a known alias) | ||
| if (init.type === "Identifier" && fragmentAliases.has(init.name)) { | ||
| fragmentAliases.add(varPath.node.id.name); | ||
| } | ||
| // Handle: const MyFragment = React.Fragment (only for known React namespaces) | ||
| if (init.type === "MemberExpression" && init.object.type === "Identifier" && init.property.type === "Identifier" && init.property.name === "Fragment" && reactNamespaceAliases.has(init.object.name)) { | ||
| fragmentAliases.add(varPath.node.id.name); | ||
| } | ||
| } | ||
| // Handle destructuring assignments: const { Fragment } = React | ||
| if (varPath.node.id.type === "ObjectPattern") { | ||
| if (init.type === "Identifier" && reactNamespaceAliases.has(init.name)) { | ||
| var properties = varPath.node.id.properties; | ||
| var _iterator = _createForOfIteratorHelper(properties), | ||
| _step; | ||
| try { | ||
| for (_iterator.s(); !(_step = _iterator.n()).done;) { | ||
| var prop = _step.value; | ||
| if (prop.type === "ObjectProperty" && prop.key && prop.key.type === "Identifier" && prop.value && prop.value.type === "Identifier" && prop.key.name === "Fragment") { | ||
| fragmentAliases.add(prop.value.name); | ||
| } | ||
| } | ||
| } catch (err) { | ||
| _iterator.e(err); | ||
| } finally { | ||
| _iterator.f(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }); | ||
| return { | ||
| fragmentAliases: fragmentAliases, | ||
| reactNamespaceAliases: reactNamespaceAliases | ||
| }; | ||
| const fragmentAliases = /* @__PURE__ */ new Set(); | ||
| const reactNamespaceAliases = new Set(["React"]); | ||
| programPath.traverse({ | ||
| ImportDeclaration(importPath) { | ||
| const source = importPath.node.source.value; | ||
| if (source === "react" || source === "React") importPath.node.specifiers.forEach((spec) => { | ||
| if (spec.type === "ImportSpecifier" && spec.imported.type === "Identifier") { | ||
| if (spec.imported.name === "Fragment") fragmentAliases.add(spec.local.name); | ||
| } else if (spec.type === "ImportDefaultSpecifier" || spec.type === "ImportNamespaceSpecifier") reactNamespaceAliases.add(spec.local.name); | ||
| }); | ||
| }, | ||
| VariableDeclarator(varPath) { | ||
| if (varPath.node.init) { | ||
| const init = varPath.node.init; | ||
| if (varPath.node.id.type === "Identifier") { | ||
| if (init.type === "Identifier" && fragmentAliases.has(init.name)) fragmentAliases.add(varPath.node.id.name); | ||
| if (init.type === "MemberExpression" && init.object.type === "Identifier" && init.property.type === "Identifier" && init.property.name === "Fragment" && reactNamespaceAliases.has(init.object.name)) fragmentAliases.add(varPath.node.id.name); | ||
| } | ||
| if (varPath.node.id.type === "ObjectPattern") { | ||
| if (init.type === "Identifier" && reactNamespaceAliases.has(init.name)) { | ||
| const properties = varPath.node.id.properties; | ||
| for (const prop of properties) if (prop.type === "ObjectProperty" && prop.key && prop.key.type === "Identifier" && prop.value && prop.value.type === "Identifier" && prop.key.name === "Fragment") fragmentAliases.add(prop.value.name); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }); | ||
| return { | ||
| fragmentAliases, | ||
| reactNamespaceAliases | ||
| }; | ||
| } | ||
| function isReactFragment$1(t, openingElement, context) { | ||
| // Handle JSX fragments (<>) | ||
| if (openingElement.isJSXFragment()) { | ||
| return true; | ||
| } | ||
| var elementName = getPathName$1(t, openingElement); | ||
| // Direct fragment references | ||
| if (elementName === "Fragment" || elementName === "React.Fragment") { | ||
| return true; | ||
| } | ||
| // TODO: All these objects are typed as unknown, maybe an oversight in Babel types? | ||
| // Check if the element name is a known fragment alias | ||
| if (context && elementName && context.fragmentAliases.has(elementName)) { | ||
| return true; | ||
| } | ||
| // Handle JSXMemberExpression | ||
| if (openingElement.node && "name" in openingElement.node && openingElement.node.name && _typeof(openingElement.node.name) === "object" && "type" in openingElement.node.name && openingElement.node.name.type === "JSXMemberExpression") { | ||
| var nodeName = openingElement.node.name; | ||
| if (_typeof(nodeName) !== "object" || !nodeName) { | ||
| return false; | ||
| } | ||
| if ("object" in nodeName && "property" in nodeName) { | ||
| var nodeNameObject = nodeName.object; | ||
| var nodeNameProperty = nodeName.property; | ||
| if (_typeof(nodeNameObject) !== "object" || _typeof(nodeNameProperty) !== "object") { | ||
| return false; | ||
| } | ||
| if (!nodeNameObject || !nodeNameProperty) { | ||
| return false; | ||
| } | ||
| var objectName = "name" in nodeNameObject && nodeNameObject.name; | ||
| var propertyName = "name" in nodeNameProperty && nodeNameProperty.name; | ||
| // React.Fragment check | ||
| if (objectName === "React" && propertyName === "Fragment") { | ||
| return true; | ||
| } | ||
| // Enhanced checks using context | ||
| if (context) { | ||
| // Check React.Fragment pattern with known React namespaces | ||
| if (context.reactNamespaceAliases.has(objectName) && propertyName === "Fragment") { | ||
| return true; | ||
| } | ||
| // Check MyFragment.Fragment pattern | ||
| if (context.fragmentAliases.has(objectName) && propertyName === "Fragment") { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return false; | ||
| if (openingElement.isJSXFragment()) return true; | ||
| const elementName = getPathName$1(t, openingElement); | ||
| if (elementName === "Fragment" || elementName === "React.Fragment") return true; | ||
| if (context && elementName && context.fragmentAliases.has(elementName)) return true; | ||
| if (openingElement.node && "name" in openingElement.node && openingElement.node.name && typeof openingElement.node.name === "object" && "type" in openingElement.node.name && openingElement.node.name.type === "JSXMemberExpression") { | ||
| const nodeName = openingElement.node.name; | ||
| if (typeof nodeName !== "object" || !nodeName) return false; | ||
| if ("object" in nodeName && "property" in nodeName) { | ||
| const nodeNameObject = nodeName.object; | ||
| const nodeNameProperty = nodeName.property; | ||
| if (typeof nodeNameObject !== "object" || typeof nodeNameProperty !== "object") return false; | ||
| if (!nodeNameObject || !nodeNameProperty) return false; | ||
| const objectName = "name" in nodeNameObject && nodeNameObject.name; | ||
| const propertyName = "name" in nodeNameProperty && nodeNameProperty.name; | ||
| if (objectName === "React" && propertyName === "Fragment") return true; | ||
| if (context) { | ||
| if (context.reactNamespaceAliases.has(objectName) && propertyName === "Fragment") return true; | ||
| if (context.fragmentAliases.has(objectName) && propertyName === "Fragment") return true; | ||
| } | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| function hasAttributeWithName$1(openingElement, name) { | ||
| if (!name) { | ||
| return false; | ||
| } | ||
| return openingElement.node.attributes.some(function (node) { | ||
| if (node.type === "JSXAttribute") { | ||
| return node.name.name === name; | ||
| } | ||
| return false; | ||
| }); | ||
| if (!name) return false; | ||
| return openingElement.node.attributes.some((node) => { | ||
| if (node.type === "JSXAttribute") return node.name.name === name; | ||
| return false; | ||
| }); | ||
| } | ||
| function getPathName$1(t, path) { | ||
| if (!path.node) return UNKNOWN_ELEMENT_NAME$1; | ||
| if (!("name" in path.node)) { | ||
| return UNKNOWN_ELEMENT_NAME$1; | ||
| } | ||
| var name = path.node.name; | ||
| if (typeof name === "string") { | ||
| return name; | ||
| } | ||
| if (t.isIdentifier(name) || t.isJSXIdentifier(name)) { | ||
| return name.name; | ||
| } | ||
| if (t.isJSXNamespacedName(name)) { | ||
| return name.name.name; | ||
| } | ||
| // Handle JSX member expressions like Tab.Group | ||
| if (t.isJSXMemberExpression(name)) { | ||
| var objectName = getJSXMemberExpressionObjectName$1(t, name.object); | ||
| var propertyName = name.property.name; | ||
| return "".concat(objectName, ".").concat(propertyName); | ||
| } | ||
| return UNKNOWN_ELEMENT_NAME$1; | ||
| if (!path.node) return UNKNOWN_ELEMENT_NAME$1; | ||
| if (!("name" in path.node)) return UNKNOWN_ELEMENT_NAME$1; | ||
| const name = path.node.name; | ||
| if (typeof name === "string") return name; | ||
| if (t.isIdentifier(name) || t.isJSXIdentifier(name)) return name.name; | ||
| if (t.isJSXNamespacedName(name)) return name.name.name; | ||
| if (t.isJSXMemberExpression(name)) return `${getJSXMemberExpressionObjectName$1(t, name.object)}.${name.property.name}`; | ||
| return UNKNOWN_ELEMENT_NAME$1; | ||
| } | ||
| // Recursively handle nested member expressions (e.g. Components.UI.Header) | ||
| function getJSXMemberExpressionObjectName$1(t, object) { | ||
| if (t.isJSXIdentifier(object)) { | ||
| return object.name; | ||
| } | ||
| if (t.isJSXMemberExpression(object)) { | ||
| var objectName = getJSXMemberExpressionObjectName$1(t, object.object); | ||
| return "".concat(objectName, ".").concat(object.property.name); | ||
| } | ||
| return UNKNOWN_ELEMENT_NAME$1; | ||
| if (t.isJSXIdentifier(object)) return object.name; | ||
| if (t.isJSXMemberExpression(object)) return `${getJSXMemberExpressionObjectName$1(t, object.object)}.${object.property.name}`; | ||
| return UNKNOWN_ELEMENT_NAME$1; | ||
| } | ||
| var UNKNOWN_ELEMENT_NAME$1 = "unknown"; | ||
| const UNKNOWN_ELEMENT_NAME$1 = "unknown"; | ||
| var webComponentName = "data-sentry-component"; | ||
| var webElementName = "data-sentry-element"; | ||
| var webSourceFileName = "data-sentry-source-file"; | ||
| var nativeComponentName = "dataSentryComponent"; | ||
| var nativeElementName = "dataSentryElement"; | ||
| var nativeSourceFileName = "dataSentrySourceFile"; | ||
| // We must export the plugin as default, otherwise the Babel loader will not be able to resolve it when configured using its string identifier | ||
| function componentNameAnnotatePlugin(_ref) { | ||
| var t = _ref.types; | ||
| return { | ||
| visitor: { | ||
| Program: { | ||
| enter: function enter(path, state) { | ||
| var fragmentContext = collectFragmentContext(path); | ||
| state.sentryFragmentContext = fragmentContext; | ||
| } | ||
| }, | ||
| FunctionDeclaration: function FunctionDeclaration(path, state) { | ||
| if (!path.node.id || !path.node.id.name) { | ||
| return; | ||
| } | ||
| if (isKnownIncompatiblePluginFromState(state)) { | ||
| return; | ||
| } | ||
| var context = createJSXProcessingContext(state, t, path.node.id.name); | ||
| functionBodyPushAttributes(context, path); | ||
| }, | ||
| ArrowFunctionExpression: function ArrowFunctionExpression(path, state) { | ||
| // We're expecting a `VariableDeclarator` like `const MyComponent =` | ||
| var parent = path.parent; | ||
| if (!parent || !("id" in parent) || !parent.id || !("name" in parent.id) || !parent.id.name) { | ||
| return; | ||
| } | ||
| if (isKnownIncompatiblePluginFromState(state)) { | ||
| return; | ||
| } | ||
| var context = createJSXProcessingContext(state, t, parent.id.name); | ||
| functionBodyPushAttributes(context, path); | ||
| }, | ||
| ClassDeclaration: function ClassDeclaration(path, state) { | ||
| var _name$node; | ||
| var name = path.get("id"); | ||
| var properties = path.get("body").get("body"); | ||
| var render = properties.find(function (prop) { | ||
| return prop.isClassMethod() && prop.get("key").isIdentifier({ | ||
| name: "render" | ||
| }); | ||
| }); | ||
| if (!render || !render.traverse || isKnownIncompatiblePluginFromState(state)) { | ||
| return; | ||
| } | ||
| var context = createJSXProcessingContext(state, t, ((_name$node = name.node) === null || _name$node === void 0 ? void 0 : _name$node.name) || ""); | ||
| render.traverse({ | ||
| ReturnStatement: function ReturnStatement(returnStatement) { | ||
| var arg = returnStatement.get("argument"); | ||
| if (!arg.isJSXElement() && !arg.isJSXFragment()) { | ||
| return; | ||
| } | ||
| processJSX(context, arg); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| }; | ||
| //#endregion | ||
| //#region src/index.ts | ||
| const webComponentName = "data-sentry-component"; | ||
| const webElementName = "data-sentry-element"; | ||
| const webSourceFileName = "data-sentry-source-file"; | ||
| const nativeComponentName = "dataSentryComponent"; | ||
| const nativeElementName = "dataSentryElement"; | ||
| const nativeSourceFileName = "dataSentrySourceFile"; | ||
| function componentNameAnnotatePlugin({ types: t }) { | ||
| return { visitor: { | ||
| Program: { enter(path, state) { | ||
| state.sentryFragmentContext = collectFragmentContext(path); | ||
| } }, | ||
| FunctionDeclaration(path, state) { | ||
| if (!path.node.id || !path.node.id.name) return; | ||
| if (isKnownIncompatiblePluginFromState(state)) return; | ||
| functionBodyPushAttributes(createJSXProcessingContext(state, t, path.node.id.name), path); | ||
| }, | ||
| ArrowFunctionExpression(path, state) { | ||
| const parent = path.parent; | ||
| if (!parent || !("id" in parent) || !parent.id || !("name" in parent.id) || !parent.id.name) return; | ||
| if (isKnownIncompatiblePluginFromState(state)) return; | ||
| functionBodyPushAttributes(createJSXProcessingContext(state, t, parent.id.name), path); | ||
| }, | ||
| ClassDeclaration(path, state) { | ||
| const name = path.get("id"); | ||
| const render = path.get("body").get("body").find((prop) => { | ||
| return prop.isClassMethod() && prop.get("key").isIdentifier({ name: "render" }); | ||
| }); | ||
| if (!render || !render.traverse || isKnownIncompatiblePluginFromState(state)) return; | ||
| const context = createJSXProcessingContext(state, t, name.node?.name || ""); | ||
| render.traverse({ ReturnStatement(returnStatement) { | ||
| const arg = returnStatement.get("argument"); | ||
| if (!arg.isJSXElement() && !arg.isJSXFragment()) return; | ||
| processJSX(context, arg); | ||
| } }); | ||
| } | ||
| } }; | ||
| } | ||
| /** | ||
| * Creates a JSX processing context from the plugin state | ||
| */ | ||
| * Creates a JSX processing context from the plugin state | ||
| */ | ||
| function createJSXProcessingContext(state, t, componentName) { | ||
| var _state$opts$ignoredCo; | ||
| return { | ||
| annotateFragments: state.opts["annotate-fragments"] === true, | ||
| t: t, | ||
| componentName: componentName, | ||
| sourceFileName: sourceFileNameFromState(state), | ||
| attributeNames: attributeNamesFromState(state), | ||
| ignoredComponents: (_state$opts$ignoredCo = state.opts.ignoredComponents) !== null && _state$opts$ignoredCo !== void 0 ? _state$opts$ignoredCo : [], | ||
| fragmentContext: state.sentryFragmentContext | ||
| }; | ||
| return { | ||
| annotateFragments: state.opts["annotate-fragments"] === true, | ||
| t, | ||
| componentName, | ||
| sourceFileName: sourceFileNameFromState(state), | ||
| attributeNames: attributeNamesFromState(state), | ||
| ignoredComponents: state.opts.ignoredComponents ?? [], | ||
| fragmentContext: state.sentryFragmentContext | ||
| }; | ||
| } | ||
| /** | ||
| * Processes the body of a function to add Sentry tracking attributes to JSX elements. | ||
| * Handles various function body structures including direct JSX returns, conditional expressions, | ||
| * and nested JSX elements. | ||
| */ | ||
| * Processes the body of a function to add Sentry tracking attributes to JSX elements. | ||
| * Handles various function body structures including direct JSX returns, conditional expressions, | ||
| * and nested JSX elements. | ||
| */ | ||
| function functionBodyPushAttributes(context, path) { | ||
| var jsxNode; | ||
| var functionBody = path.get("body").get("body"); | ||
| if (!("length" in functionBody) && functionBody.parent && (functionBody.parent.type === "JSXElement" || functionBody.parent.type === "JSXFragment")) { | ||
| var maybeJsxNode = functionBody.find(function (c) { | ||
| return c.type === "JSXElement" || c.type === "JSXFragment"; | ||
| }); | ||
| if (!maybeJsxNode) { | ||
| return; | ||
| } | ||
| jsxNode = maybeJsxNode; | ||
| } else { | ||
| var returnStatement = functionBody.find(function (c) { | ||
| return c.type === "ReturnStatement"; | ||
| }); | ||
| if (!returnStatement) { | ||
| return; | ||
| } | ||
| var arg = returnStatement.get("argument"); | ||
| if (!arg) { | ||
| return; | ||
| } | ||
| if (Array.isArray(arg)) { | ||
| return; | ||
| } | ||
| // Handle the case of a function body returning a ternary operation. | ||
| // `return (maybeTrue ? '' : (<SubComponent />))` | ||
| if (arg.isConditionalExpression()) { | ||
| var consequent = arg.get("consequent"); | ||
| if (consequent.isJSXFragment() || consequent.isJSXElement()) { | ||
| processJSX(context, consequent); | ||
| } | ||
| var alternate = arg.get("alternate"); | ||
| if (alternate.isJSXFragment() || alternate.isJSXElement()) { | ||
| processJSX(context, alternate); | ||
| } | ||
| return; | ||
| } | ||
| if (!arg.isJSXFragment() && !arg.isJSXElement()) { | ||
| return; | ||
| } | ||
| jsxNode = arg; | ||
| } | ||
| if (!jsxNode) { | ||
| return; | ||
| } | ||
| processJSX(context, jsxNode); | ||
| let jsxNode; | ||
| const functionBody = path.get("body").get("body"); | ||
| if (!("length" in functionBody) && functionBody.parent && (functionBody.parent.type === "JSXElement" || functionBody.parent.type === "JSXFragment")) { | ||
| const maybeJsxNode = functionBody.find((c) => { | ||
| return c.type === "JSXElement" || c.type === "JSXFragment"; | ||
| }); | ||
| if (!maybeJsxNode) return; | ||
| jsxNode = maybeJsxNode; | ||
| } else { | ||
| const returnStatement = functionBody.find((c) => { | ||
| return c.type === "ReturnStatement"; | ||
| }); | ||
| if (!returnStatement) return; | ||
| const arg = returnStatement.get("argument"); | ||
| if (!arg) return; | ||
| if (Array.isArray(arg)) return; | ||
| if (arg.isConditionalExpression()) { | ||
| const consequent = arg.get("consequent"); | ||
| if (consequent.isJSXFragment() || consequent.isJSXElement()) processJSX(context, consequent); | ||
| const alternate = arg.get("alternate"); | ||
| if (alternate.isJSXFragment() || alternate.isJSXElement()) processJSX(context, alternate); | ||
| return; | ||
| } | ||
| if (!arg.isJSXFragment() && !arg.isJSXElement()) return; | ||
| jsxNode = arg; | ||
| } | ||
| if (!jsxNode) return; | ||
| processJSX(context, jsxNode); | ||
| } | ||
| /** | ||
| * Recursively processes JSX elements to add Sentry tracking attributes. | ||
| * Handles both JSX elements and fragments, applying appropriate attributes | ||
| * based on configuration and component context. | ||
| */ | ||
| * Recursively processes JSX elements to add Sentry tracking attributes. | ||
| * Handles both JSX elements and fragments, applying appropriate attributes | ||
| * based on configuration and component context. | ||
| */ | ||
| function processJSX(context, jsxNode, componentName) { | ||
| if (!jsxNode) { | ||
| return; | ||
| } | ||
| // Use provided componentName or fall back to context componentName | ||
| var currentComponentName = componentName !== null && componentName !== void 0 ? componentName : context.componentName; | ||
| // NOTE: I don't know of a case where `openingElement` would have more than one item, | ||
| // but it's safer to always iterate | ||
| var paths = jsxNode.get("openingElement"); | ||
| var openingElements = Array.isArray(paths) ? paths : [paths]; | ||
| openingElements.forEach(function (openingElement) { | ||
| applyAttributes(context, openingElement, currentComponentName); | ||
| }); | ||
| var children = jsxNode.get("children"); | ||
| // TODO: See why `Array.isArray` doesn't have correct behaviour here | ||
| if (children && !("length" in children)) { | ||
| // A single child was found, maybe a bit of static text | ||
| children = [children]; | ||
| } | ||
| var shouldSetComponentName = context.annotateFragments; | ||
| children.forEach(function (child) { | ||
| // Happens for some node types like plain text | ||
| if (!child.node) { | ||
| return; | ||
| } | ||
| // Children don't receive the data-component attribute so we pass null for componentName unless it's the first child of a Fragment with a node and `annotateFragments` is true | ||
| var openingElement = child.get("openingElement"); | ||
| // TODO: Improve this. We never expect to have multiple opening elements | ||
| // but if it's possible, this should work | ||
| if (Array.isArray(openingElement)) { | ||
| return; | ||
| } | ||
| if (shouldSetComponentName && openingElement && openingElement.node) { | ||
| shouldSetComponentName = false; | ||
| processJSX(context, child, currentComponentName); | ||
| } else { | ||
| processJSX(context, child, ""); | ||
| } | ||
| }); | ||
| if (!jsxNode) return; | ||
| const currentComponentName = componentName ?? context.componentName; | ||
| const paths = jsxNode.get("openingElement"); | ||
| (Array.isArray(paths) ? paths : [paths]).forEach((openingElement) => { | ||
| applyAttributes(context, openingElement, currentComponentName); | ||
| }); | ||
| let children = jsxNode.get("children"); | ||
| if (children && !("length" in children)) children = [children]; | ||
| let shouldSetComponentName = context.annotateFragments; | ||
| children.forEach((child) => { | ||
| if (!child.node) return; | ||
| const openingElement = child.get("openingElement"); | ||
| if (Array.isArray(openingElement)) return; | ||
| if (shouldSetComponentName && openingElement && openingElement.node) { | ||
| shouldSetComponentName = false; | ||
| processJSX(context, child, currentComponentName); | ||
| } else processJSX(context, child, ""); | ||
| }); | ||
| } | ||
| /** | ||
| * Applies Sentry tracking attributes to a JSX opening element. | ||
| * Adds component name, element name, and source file attributes while | ||
| * respecting ignore lists and fragment detection. | ||
| */ | ||
| * Applies Sentry tracking attributes to a JSX opening element. | ||
| * Adds component name, element name, and source file attributes while | ||
| * respecting ignore lists and fragment detection. | ||
| */ | ||
| function applyAttributes(context, openingElement, componentName) { | ||
| var t = context.t, | ||
| attributeNames = context.attributeNames, | ||
| ignoredComponents = context.ignoredComponents, | ||
| fragmentContext = context.fragmentContext, | ||
| sourceFileName = context.sourceFileName; | ||
| var _attributeNames = _slicedToArray(attributeNames, 3), | ||
| componentAttributeName = _attributeNames[0], | ||
| elementAttributeName = _attributeNames[1], | ||
| sourceFileAttributeName = _attributeNames[2]; | ||
| // e.g., Raw JSX text like the `A` in `<h1>a</h1>` | ||
| if (!openingElement.node) { | ||
| return; | ||
| } | ||
| // Check if this is a React fragment - if so, skip attribute addition entirely | ||
| var isFragment = isReactFragment(t, openingElement, fragmentContext); | ||
| if (isFragment) { | ||
| return; | ||
| } | ||
| if (!openingElement.node.attributes) openingElement.node.attributes = []; | ||
| var elementName = getPathName(t, openingElement); | ||
| var isAnIgnoredComponent = ignoredComponents.some(function (ignoredComponent) { | ||
| return ignoredComponent === componentName || ignoredComponent === elementName; | ||
| }); | ||
| // Add a stable attribute for the element name but only for non-DOM names | ||
| var isAnIgnoredElement = false; | ||
| if (!isAnIgnoredComponent && !hasAttributeWithName(openingElement, elementAttributeName)) { | ||
| if (DEFAULT_IGNORED_ELEMENTS.includes(elementName)) { | ||
| isAnIgnoredElement = true; | ||
| } else { | ||
| // Always add element attribute for non-ignored elements | ||
| if (elementAttributeName) { | ||
| openingElement.node.attributes.push(t.jSXAttribute(t.jSXIdentifier(elementAttributeName), t.stringLiteral(elementName))); | ||
| } | ||
| } | ||
| } | ||
| // Add a stable attribute for the component name (absent for non-root elements) | ||
| if (componentName && !isAnIgnoredComponent && !hasAttributeWithName(openingElement, componentAttributeName)) { | ||
| if (componentAttributeName) { | ||
| openingElement.node.attributes.push(t.jSXAttribute(t.jSXIdentifier(componentAttributeName), t.stringLiteral(componentName))); | ||
| } | ||
| } | ||
| // Add a stable attribute for the source file name | ||
| // Updated condition: add source file for elements that have either: | ||
| // 1. A component name (root elements), OR | ||
| // 2. An element name that's not ignored (child elements) | ||
| if (sourceFileName && !isAnIgnoredComponent && (componentName || !isAnIgnoredElement) && !hasAttributeWithName(openingElement, sourceFileAttributeName)) { | ||
| if (sourceFileAttributeName) { | ||
| openingElement.node.attributes.push(t.jSXAttribute(t.jSXIdentifier(sourceFileAttributeName), t.stringLiteral(sourceFileName))); | ||
| } | ||
| } | ||
| const { t, attributeNames, ignoredComponents, fragmentContext, sourceFileName } = context; | ||
| const [componentAttributeName, elementAttributeName, sourceFileAttributeName] = attributeNames; | ||
| if (!openingElement.node) return; | ||
| if (isReactFragment(t, openingElement, fragmentContext)) return; | ||
| if (!openingElement.node.attributes) openingElement.node.attributes = []; | ||
| const elementName = getPathName(t, openingElement); | ||
| const isAnIgnoredComponent = ignoredComponents.some((ignoredComponent) => ignoredComponent === componentName || ignoredComponent === elementName); | ||
| let isAnIgnoredElement = false; | ||
| if (!isAnIgnoredComponent && !hasAttributeWithName(openingElement, elementAttributeName)) { | ||
| if (DEFAULT_IGNORED_ELEMENTS.includes(elementName)) isAnIgnoredElement = true; | ||
| else if (elementAttributeName) openingElement.node.attributes.push(t.jSXAttribute(t.jSXIdentifier(elementAttributeName), t.stringLiteral(elementName))); | ||
| } | ||
| if (componentName && !isAnIgnoredComponent && !hasAttributeWithName(openingElement, componentAttributeName)) { | ||
| if (componentAttributeName) openingElement.node.attributes.push(t.jSXAttribute(t.jSXIdentifier(componentAttributeName), t.stringLiteral(componentName))); | ||
| } | ||
| if (sourceFileName && !isAnIgnoredComponent && (componentName || !isAnIgnoredElement) && !hasAttributeWithName(openingElement, sourceFileAttributeName)) { | ||
| if (sourceFileAttributeName) openingElement.node.attributes.push(t.jSXAttribute(t.jSXIdentifier(sourceFileAttributeName), t.stringLiteral(sourceFileName))); | ||
| } | ||
| } | ||
| function sourceFileNameFromState(state) { | ||
| var name = fullSourceFileNameFromState(state); | ||
| if (!name) { | ||
| return undefined; | ||
| } | ||
| if (name.indexOf("/") !== -1) { | ||
| return name.split("/").pop(); | ||
| } else if (name.indexOf("\\") !== -1) { | ||
| return name.split("\\").pop(); | ||
| } else { | ||
| return name; | ||
| } | ||
| const name = fullSourceFileNameFromState(state); | ||
| if (!name) return; | ||
| if (name.indexOf("/") !== -1) return name.split("/").pop(); | ||
| else if (name.indexOf("\\") !== -1) return name.split("\\").pop(); | ||
| else return name; | ||
| } | ||
| function fullSourceFileNameFromState(state) { | ||
| var _state$file$opts$pars; | ||
| // @ts-expect-error This type is incorrect in Babel, `sourceFileName` is the correct type | ||
| var name = (_state$file$opts$pars = state.file.opts.parserOpts) === null || _state$file$opts$pars === void 0 ? void 0 : _state$file$opts$pars.sourceFileName; | ||
| if (typeof name === "string") { | ||
| return name; | ||
| } | ||
| return null; | ||
| const name = state.file.opts.parserOpts?.sourceFileName; | ||
| if (typeof name === "string") return name; | ||
| return null; | ||
| } | ||
| function isKnownIncompatiblePluginFromState(state) { | ||
| var fullSourceFileName = fullSourceFileNameFromState(state); | ||
| if (!fullSourceFileName) { | ||
| return false; | ||
| } | ||
| return KNOWN_INCOMPATIBLE_PLUGINS.some(function (pluginName) { | ||
| if (fullSourceFileName.includes("/node_modules/".concat(pluginName, "/")) || fullSourceFileName.includes("\\node_modules\\".concat(pluginName, "\\"))) { | ||
| return true; | ||
| } | ||
| return false; | ||
| }); | ||
| const fullSourceFileName = fullSourceFileNameFromState(state); | ||
| if (!fullSourceFileName) return false; | ||
| return KNOWN_INCOMPATIBLE_PLUGINS.some((pluginName) => { | ||
| if (fullSourceFileName.includes(`/node_modules/${pluginName}/`) || fullSourceFileName.includes(`\\node_modules\\${pluginName}\\`)) return true; | ||
| return false; | ||
| }); | ||
| } | ||
| function attributeNamesFromState(state) { | ||
| if (state.opts["native"]) { | ||
| return [nativeComponentName, nativeElementName, nativeSourceFileName]; | ||
| } | ||
| return [webComponentName, webElementName, webSourceFileName]; | ||
| if (state.opts.native) return [ | ||
| nativeComponentName, | ||
| nativeElementName, | ||
| nativeSourceFileName | ||
| ]; | ||
| return [ | ||
| webComponentName, | ||
| webElementName, | ||
| webSourceFileName | ||
| ]; | ||
| } | ||
| function collectFragmentContext(programPath) { | ||
| var fragmentAliases = new Set(); | ||
| var reactNamespaceAliases = new Set(["React"]); // Default React namespace | ||
| programPath.traverse({ | ||
| ImportDeclaration: function ImportDeclaration(importPath) { | ||
| var source = importPath.node.source.value; | ||
| // Handle React imports | ||
| if (source === "react" || source === "React") { | ||
| importPath.node.specifiers.forEach(function (spec) { | ||
| if (spec.type === "ImportSpecifier" && spec.imported.type === "Identifier") { | ||
| // Detect aliased React.Fragment imports (e.g., `Fragment as F`) | ||
| // so we can later identify <F> as a fragment in JSX. | ||
| if (spec.imported.name === "Fragment") { | ||
| fragmentAliases.add(spec.local.name); | ||
| } | ||
| } else if (spec.type === "ImportDefaultSpecifier" || spec.type === "ImportNamespaceSpecifier") { | ||
| // import React from 'react' -> React OR | ||
| // import * as React from 'react' -> React | ||
| reactNamespaceAliases.add(spec.local.name); | ||
| } | ||
| }); | ||
| } | ||
| }, | ||
| // Handle simple variable assignments only (avoid complex cases) | ||
| VariableDeclarator: function VariableDeclarator(varPath) { | ||
| if (varPath.node.init) { | ||
| var init = varPath.node.init; | ||
| // Handle identifier assignments: const MyFragment = Fragment | ||
| if (varPath.node.id.type === "Identifier") { | ||
| // Handle: const MyFragment = Fragment (only if Fragment is a known alias) | ||
| if (init.type === "Identifier" && fragmentAliases.has(init.name)) { | ||
| fragmentAliases.add(varPath.node.id.name); | ||
| } | ||
| // Handle: const MyFragment = React.Fragment (only for known React namespaces) | ||
| if (init.type === "MemberExpression" && init.object.type === "Identifier" && init.property.type === "Identifier" && init.property.name === "Fragment" && reactNamespaceAliases.has(init.object.name)) { | ||
| fragmentAliases.add(varPath.node.id.name); | ||
| } | ||
| } | ||
| // Handle destructuring assignments: const { Fragment } = React | ||
| if (varPath.node.id.type === "ObjectPattern") { | ||
| if (init.type === "Identifier" && reactNamespaceAliases.has(init.name)) { | ||
| var properties = varPath.node.id.properties; | ||
| var _iterator = _createForOfIteratorHelper(properties), | ||
| _step; | ||
| try { | ||
| for (_iterator.s(); !(_step = _iterator.n()).done;) { | ||
| var prop = _step.value; | ||
| if (prop.type === "ObjectProperty" && prop.key && prop.key.type === "Identifier" && prop.value && prop.value.type === "Identifier" && prop.key.name === "Fragment") { | ||
| fragmentAliases.add(prop.value.name); | ||
| } | ||
| } | ||
| } catch (err) { | ||
| _iterator.e(err); | ||
| } finally { | ||
| _iterator.f(); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }); | ||
| return { | ||
| fragmentAliases: fragmentAliases, | ||
| reactNamespaceAliases: reactNamespaceAliases | ||
| }; | ||
| const fragmentAliases = /* @__PURE__ */ new Set(); | ||
| const reactNamespaceAliases = new Set(["React"]); | ||
| programPath.traverse({ | ||
| ImportDeclaration(importPath) { | ||
| const source = importPath.node.source.value; | ||
| if (source === "react" || source === "React") importPath.node.specifiers.forEach((spec) => { | ||
| if (spec.type === "ImportSpecifier" && spec.imported.type === "Identifier") { | ||
| if (spec.imported.name === "Fragment") fragmentAliases.add(spec.local.name); | ||
| } else if (spec.type === "ImportDefaultSpecifier" || spec.type === "ImportNamespaceSpecifier") reactNamespaceAliases.add(spec.local.name); | ||
| }); | ||
| }, | ||
| VariableDeclarator(varPath) { | ||
| if (varPath.node.init) { | ||
| const init = varPath.node.init; | ||
| if (varPath.node.id.type === "Identifier") { | ||
| if (init.type === "Identifier" && fragmentAliases.has(init.name)) fragmentAliases.add(varPath.node.id.name); | ||
| if (init.type === "MemberExpression" && init.object.type === "Identifier" && init.property.type === "Identifier" && init.property.name === "Fragment" && reactNamespaceAliases.has(init.object.name)) fragmentAliases.add(varPath.node.id.name); | ||
| } | ||
| if (varPath.node.id.type === "ObjectPattern") { | ||
| if (init.type === "Identifier" && reactNamespaceAliases.has(init.name)) { | ||
| const properties = varPath.node.id.properties; | ||
| for (const prop of properties) if (prop.type === "ObjectProperty" && prop.key && prop.key.type === "Identifier" && prop.value && prop.value.type === "Identifier" && prop.key.name === "Fragment") fragmentAliases.add(prop.value.name); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| }); | ||
| return { | ||
| fragmentAliases, | ||
| reactNamespaceAliases | ||
| }; | ||
| } | ||
| function isReactFragment(t, openingElement, context) { | ||
| // Handle JSX fragments (<>) | ||
| if (openingElement.isJSXFragment()) { | ||
| return true; | ||
| } | ||
| var elementName = getPathName(t, openingElement); | ||
| // Direct fragment references | ||
| if (elementName === "Fragment" || elementName === "React.Fragment") { | ||
| return true; | ||
| } | ||
| // TODO: All these objects are typed as unknown, maybe an oversight in Babel types? | ||
| // Check if the element name is a known fragment alias | ||
| if (context && elementName && context.fragmentAliases.has(elementName)) { | ||
| return true; | ||
| } | ||
| // Handle JSXMemberExpression | ||
| if (openingElement.node && "name" in openingElement.node && openingElement.node.name && _typeof(openingElement.node.name) === "object" && "type" in openingElement.node.name && openingElement.node.name.type === "JSXMemberExpression") { | ||
| var nodeName = openingElement.node.name; | ||
| if (_typeof(nodeName) !== "object" || !nodeName) { | ||
| return false; | ||
| } | ||
| if ("object" in nodeName && "property" in nodeName) { | ||
| var nodeNameObject = nodeName.object; | ||
| var nodeNameProperty = nodeName.property; | ||
| if (_typeof(nodeNameObject) !== "object" || _typeof(nodeNameProperty) !== "object") { | ||
| return false; | ||
| } | ||
| if (!nodeNameObject || !nodeNameProperty) { | ||
| return false; | ||
| } | ||
| var objectName = "name" in nodeNameObject && nodeNameObject.name; | ||
| var propertyName = "name" in nodeNameProperty && nodeNameProperty.name; | ||
| // React.Fragment check | ||
| if (objectName === "React" && propertyName === "Fragment") { | ||
| return true; | ||
| } | ||
| // Enhanced checks using context | ||
| if (context) { | ||
| // Check React.Fragment pattern with known React namespaces | ||
| if (context.reactNamespaceAliases.has(objectName) && propertyName === "Fragment") { | ||
| return true; | ||
| } | ||
| // Check MyFragment.Fragment pattern | ||
| if (context.fragmentAliases.has(objectName) && propertyName === "Fragment") { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| return false; | ||
| if (openingElement.isJSXFragment()) return true; | ||
| const elementName = getPathName(t, openingElement); | ||
| if (elementName === "Fragment" || elementName === "React.Fragment") return true; | ||
| if (context && elementName && context.fragmentAliases.has(elementName)) return true; | ||
| if (openingElement.node && "name" in openingElement.node && openingElement.node.name && typeof openingElement.node.name === "object" && "type" in openingElement.node.name && openingElement.node.name.type === "JSXMemberExpression") { | ||
| const nodeName = openingElement.node.name; | ||
| if (typeof nodeName !== "object" || !nodeName) return false; | ||
| if ("object" in nodeName && "property" in nodeName) { | ||
| const nodeNameObject = nodeName.object; | ||
| const nodeNameProperty = nodeName.property; | ||
| if (typeof nodeNameObject !== "object" || typeof nodeNameProperty !== "object") return false; | ||
| if (!nodeNameObject || !nodeNameProperty) return false; | ||
| const objectName = "name" in nodeNameObject && nodeNameObject.name; | ||
| const propertyName = "name" in nodeNameProperty && nodeNameProperty.name; | ||
| if (objectName === "React" && propertyName === "Fragment") return true; | ||
| if (context) { | ||
| if (context.reactNamespaceAliases.has(objectName) && propertyName === "Fragment") return true; | ||
| if (context.fragmentAliases.has(objectName) && propertyName === "Fragment") return true; | ||
| } | ||
| } | ||
| } | ||
| return false; | ||
| } | ||
| function hasAttributeWithName(openingElement, name) { | ||
| if (!name) { | ||
| return false; | ||
| } | ||
| return openingElement.node.attributes.some(function (node) { | ||
| if (node.type === "JSXAttribute") { | ||
| return node.name.name === name; | ||
| } | ||
| return false; | ||
| }); | ||
| if (!name) return false; | ||
| return openingElement.node.attributes.some((node) => { | ||
| if (node.type === "JSXAttribute") return node.name.name === name; | ||
| return false; | ||
| }); | ||
| } | ||
| function getPathName(t, path) { | ||
| if (!path.node) return UNKNOWN_ELEMENT_NAME; | ||
| if (!("name" in path.node)) { | ||
| return UNKNOWN_ELEMENT_NAME; | ||
| } | ||
| var name = path.node.name; | ||
| if (typeof name === "string") { | ||
| return name; | ||
| } | ||
| if (t.isIdentifier(name) || t.isJSXIdentifier(name)) { | ||
| return name.name; | ||
| } | ||
| if (t.isJSXNamespacedName(name)) { | ||
| return name.name.name; | ||
| } | ||
| // Handle JSX member expressions like Tab.Group | ||
| if (t.isJSXMemberExpression(name)) { | ||
| var objectName = getJSXMemberExpressionObjectName(t, name.object); | ||
| var propertyName = name.property.name; | ||
| return "".concat(objectName, ".").concat(propertyName); | ||
| } | ||
| return UNKNOWN_ELEMENT_NAME; | ||
| if (!path.node) return UNKNOWN_ELEMENT_NAME; | ||
| if (!("name" in path.node)) return UNKNOWN_ELEMENT_NAME; | ||
| const name = path.node.name; | ||
| if (typeof name === "string") return name; | ||
| if (t.isIdentifier(name) || t.isJSXIdentifier(name)) return name.name; | ||
| if (t.isJSXNamespacedName(name)) return name.name.name; | ||
| if (t.isJSXMemberExpression(name)) return `${getJSXMemberExpressionObjectName(t, name.object)}.${name.property.name}`; | ||
| return UNKNOWN_ELEMENT_NAME; | ||
| } | ||
| // Recursively handle nested member expressions (e.g. Components.UI.Header) | ||
| function getJSXMemberExpressionObjectName(t, object) { | ||
| if (t.isJSXIdentifier(object)) { | ||
| return object.name; | ||
| } | ||
| if (t.isJSXMemberExpression(object)) { | ||
| var objectName = getJSXMemberExpressionObjectName(t, object.object); | ||
| return "".concat(objectName, ".").concat(object.property.name); | ||
| } | ||
| return UNKNOWN_ELEMENT_NAME; | ||
| if (t.isJSXIdentifier(object)) return object.name; | ||
| if (t.isJSXMemberExpression(object)) return `${getJSXMemberExpressionObjectName(t, object.object)}.${object.property.name}`; | ||
| return UNKNOWN_ELEMENT_NAME; | ||
| } | ||
| var UNKNOWN_ELEMENT_NAME = "unknown"; | ||
| const UNKNOWN_ELEMENT_NAME = "unknown"; | ||
| //#endregion | ||
| export { componentNameAnnotatePlugin as default, experimentalComponentNameAnnotatePlugin }; | ||
| //# sourceMappingURL=index.mjs.map | ||
| //# sourceMappingURL=index.mjs.map |
+6
-10
| { | ||
| "name": "@sentry/babel-plugin-component-annotate", | ||
| "version": "4.9.1", | ||
| "version": "5.0.0", | ||
| "description": "A Babel plugin that annotates frontend components with additional data to enrich the experience in Sentry", | ||
@@ -37,4 +37,4 @@ "repository": "git://github.com/getsentry/sentry-javascript-bundler-plugins.git", | ||
| "build:watch": "run-p build:rollup:watch build:types:watch", | ||
| "build:rollup": "rollup --config rollup.config.js", | ||
| "build:rollup:watch": "rollup --config rollup.config.js --watch --no-watch.clearScreen", | ||
| "build:rollup": "rolldown --config rollup.config.mjs", | ||
| "build:rollup:watch": "rolldown --config rollup.config.mjs --watch --no-watch.clearScreen", | ||
| "build:types": "tsc --project types.tsconfig.json", | ||
@@ -55,9 +55,5 @@ "build:types:watch": "tsc --project types.tsconfig.json --watch --preserveWatchOutput", | ||
| "@babel/core": "7.18.5", | ||
| "@babel/preset-env": "7.18.2", | ||
| "@babel/preset-react": "^7.23.3", | ||
| "@babel/preset-typescript": "7.17.12", | ||
| "@rollup/plugin-babel": "5.3.1", | ||
| "@rollup/plugin-node-resolve": "13.3.0", | ||
| "@sentry-internal/eslint-config": "4.9.1", | ||
| "@sentry-internal/sentry-bundler-plugin-tsconfig": "4.9.1", | ||
| "@sentry-internal/eslint-config": "5.0.0", | ||
| "@sentry-internal/sentry-bundler-plugin-tsconfig": "5.0.0", | ||
| "@swc/core": "^1.2.205", | ||
@@ -71,3 +67,3 @@ "@swc/jest": "^0.2.21", | ||
| "premove": "^4.0.0", | ||
| "rollup": "2.75.7", | ||
| "rolldown": "^1.0.0-rc.4", | ||
| "ts-node": "^10.9.1", | ||
@@ -74,0 +70,0 @@ "typescript": "^4.7.4" |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
15
-21.05%1
-90.91%173152
-30.35%1370
-35.89%1
Infinity%