From c0c37063e2cd8976d839998573012d48d303ada0 Mon Sep 17 00:00:00 2001
From: "Sebastian \"Sebbie\" Silbermann"
Date: Wed, 28 Jan 2026 18:41:08 +0100
Subject: [PATCH 1/5] [Flight] Restore original function name in dev, server
callstacks served with `unsafe-eval` (#35650)
---
.../react-client/src/ReactFlightClient.js | 8 +++
.../src/__tests__/ReactFlight-test.js | 50 +++++++++++++++++++
2 files changed, 58 insertions(+)
diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js
index 2246f74697e..4dc316de366 100644
--- a/packages/react-client/src/ReactFlightClient.js
+++ b/packages/react-client/src/ReactFlightClient.js
@@ -3730,6 +3730,14 @@ function createFakeFunction(
fn = function (_) {
return _();
};
+ // Using the usual {[name]: _() => _()}.bind() trick to avoid minifiers
+ // doesn't work here since this will produce `Object.*` names.
+ Object.defineProperty(
+ fn,
+ // $FlowFixMe[cannot-write] -- `name` is configurable though.
+ 'name',
+ {value: name},
+ );
}
return fn;
}
diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js
index 9897c28d7e8..f62ec321501 100644
--- a/packages/react-client/src/__tests__/ReactFlight-test.js
+++ b/packages/react-client/src/__tests__/ReactFlight-test.js
@@ -3677,6 +3677,56 @@ describe('ReactFlight', () => {
expect(caughtError.digest).toBe('digest("my-error")');
});
+ it('can transport function names in stackframes in dev even without eval', async () => {
+ function a() {
+ return b();
+ }
+ function b() {
+ return c();
+ }
+ function c() {
+ return new Error('boom');
+ }
+
+ // eslint-disable-next-line no-eval
+ const previousEval = globalThis.eval.bind(globalThis);
+ // eslint-disable-next-line no-eval
+ globalThis.eval = () => {
+ throw new Error('eval is disabled');
+ };
+
+ try {
+ const transport = ReactNoopFlightServer.render(
+ {model: a()},
+ {onError: () => 'digest'},
+ );
+
+ const root = await ReactNoopFlightClient.read(transport);
+ const receivedError = await root.model;
+
+ if (__DEV__) {
+ const normalizedErrorStack = normalizeCodeLocInfo(
+ receivedError.stack.split('\n').slice(0, 4).join('\n'),
+ );
+
+ expect(normalizedErrorStack).toEqual(
+ 'Error: boom' +
+ '\n in c (at **)' +
+ '\n in b (at **)' +
+ '\n in a (at **)',
+ );
+ } else {
+ expect(receivedError.message).toEqual(
+ 'An error occurred in the Server Components render. The specific message is omitted in production builds to avoid leaking sensitive details. A digest property is included on this error instance which may provide additional details about the nature of the error.',
+ );
+ expect(receivedError).not.toHaveProperty('digest');
+ }
+ } finally {
+ // eslint-disable-next-line no-eval
+ globalThis.eval = previousEval;
+ }
+ });
+
// @gate __DEV__ && enableComponentPerformanceTrack
it('can render deep but cut off JSX in debug info', async () => {
function createDeepJSX(n) {
From d4d099f05bead14cc78787f97f005b00feae56f9 Mon Sep 17 00:00:00 2001
From: Jan Olaf Martin
Date: Wed, 28 Jan 2026 10:15:33 -0800
Subject: [PATCH 2/5] [flags] make `enableTrustedTypesIntegration` dynamic
(#35646)
Co-authored-by: Rick Hanlon
---
.../src/__tests__/ReactDOMAttribute-test.js | 8 ++-
.../src/__tests__/ReactDOMFloat-test.js | 16 ++++++
.../src/__tests__/ReactDOMForm-test.js | 12 +++-
...ctDOMServerIntegrationUntrustedURL-test.js | 5 ++
.../src/__tests__/ReactEmptyComponent-test.js | 14 +++++
.../__tests__/trustedTypes-test.internal.js | 55 +++++++++++++------
packages/shared/CheckStringCoercion.js | 2 +
.../forks/ReactFeatureFlags.www-dynamic.js | 3 +-
8 files changed, 93 insertions(+), 22 deletions(-)
diff --git a/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js b/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js
index cd1d055c09d..63baa38c710 100644
--- a/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js
@@ -171,7 +171,13 @@ describe('ReactDOM unknown attribute', () => {
const test = () =>
testUnknownAttributeAssignment(new TemporalLike(), null);
- await expect(test).rejects.toThrowError(new TypeError('prod message'));
+ if (gate('enableTrustedTypesIntegration') && !__DEV__) {
+ // TODO: this still throws in DEV even though it's not toString'd in prod.
+ await expect(test).rejects.toThrowError('2020-01-01');
+ } else {
+ await expect(test).rejects.toThrowError(new TypeError('prod message'));
+ }
+
assertConsoleErrorDev([
'The provided `unknown` attribute is an unsupported type TemporalLike.' +
' This value must be coerced to a string before using it here.\n' +
diff --git a/packages/react-dom/src/__tests__/ReactDOMFloat-test.js b/packages/react-dom/src/__tests__/ReactDOMFloat-test.js
index 1766db2de8b..1caa5ed8d6e 100644
--- a/packages/react-dom/src/__tests__/ReactDOMFloat-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMFloat-test.js
@@ -602,6 +602,14 @@ describe('ReactDOMFloat', () => {
'> ');
+ if (gate('enableTrustedTypesIntegration')) {
+ assertConsoleErrorDev([
+ 'Encountered a script tag while rendering React component. ' +
+ 'Scripts inside React components are never executed when rendering on the client. ' +
+ 'Consider using template tag instead (https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).\n' +
+ ' in script (at **)\n' +
+ ' in TogglingComponent (at **)',
+ ]);
+ }
+
const container2 = document.createElement('div');
const root2 = ReactDOMClient.createRoot(container2);
expect(() => {
@@ -189,6 +202,7 @@ describe('ReactEmptyComponent', () => {
'mount SCRIPT',
'update undefined',
]);
+ expect(container2.innerHTML).toBe('');
});
it(
diff --git a/packages/react-dom/src/client/__tests__/trustedTypes-test.internal.js b/packages/react-dom/src/client/__tests__/trustedTypes-test.internal.js
index 5a43a9ec2f0..06744581ae9 100644
--- a/packages/react-dom/src/client/__tests__/trustedTypes-test.internal.js
+++ b/packages/react-dom/src/client/__tests__/trustedTypes-test.internal.js
@@ -12,7 +12,6 @@
describe('when Trusted Types are available in global object', () => {
let React;
let ReactDOMClient;
- let ReactFeatureFlags;
let act;
let assertConsoleErrorDev;
let container;
@@ -33,8 +32,6 @@ describe('when Trusted Types are available in global object', () => {
isScript: () => false,
isScriptURL: () => false,
};
- ReactFeatureFlags = require('shared/ReactFeatureFlags');
- ReactFeatureFlags.enableTrustedTypesIntegration = true;
React = require('react');
ReactDOMClient = require('react-dom/client');
({act, assertConsoleErrorDev} = require('internal-test-utils'));
@@ -118,7 +115,11 @@ describe('when Trusted Types are available in global object', () => {
expect(setAttributeCalls[0][0]).toBe(container.firstChild);
expect(setAttributeCalls[0][1]).toBe('data-foo');
// Ensure it didn't get stringified when passed to a DOM sink:
- expect(setAttributeCalls[0][2]).toBe(ttObject1);
+ if (gate('enableTrustedTypesIntegration')) {
+ expect(setAttributeCalls[0][2]).toBe(ttObject1);
+ } else {
+ expect(setAttributeCalls[0][2]).toBe('Hi');
+ }
setAttributeCalls.length = 0;
await act(() => {
@@ -129,7 +130,11 @@ describe('when Trusted Types are available in global object', () => {
expect(setAttributeCalls[0][0]).toBe(container.firstChild);
expect(setAttributeCalls[0][1]).toBe('data-foo');
// Ensure it didn't get stringified when passed to a DOM sink:
- expect(setAttributeCalls[0][2]).toBe(ttObject2);
+ if (gate('enableTrustedTypesIntegration')) {
+ expect(setAttributeCalls[0][2]).toBe(ttObject2);
+ } else {
+ expect(setAttributeCalls[0][2]).toBe('Bye');
+ }
} finally {
Element.prototype.setAttribute = setAttribute;
}
@@ -153,7 +158,11 @@ describe('when Trusted Types are available in global object', () => {
expect(setAttributeCalls[0][0]).toBe(container.firstChild);
expect(setAttributeCalls[0][1]).toBe('class');
// Ensure it didn't get stringified when passed to a DOM sink:
- expect(setAttributeCalls[0][2]).toBe(ttObject1);
+ if (gate('enableTrustedTypesIntegration')) {
+ expect(setAttributeCalls[0][2]).toBe(ttObject1);
+ } else {
+ expect(setAttributeCalls[0][2]).toBe('Hi');
+ }
setAttributeCalls.length = 0;
await act(() => {
@@ -164,7 +173,11 @@ describe('when Trusted Types are available in global object', () => {
expect(setAttributeCalls[0][0]).toBe(container.firstChild);
expect(setAttributeCalls[0][1]).toBe('class');
// Ensure it didn't get stringified when passed to a DOM sink:
- expect(setAttributeCalls[0][2]).toBe(ttObject2);
+ if (gate('enableTrustedTypesIntegration')) {
+ expect(setAttributeCalls[0][2]).toBe(ttObject2);
+ } else {
+ expect(setAttributeCalls[0][2]).toBe('Bye');
+ }
} finally {
Element.prototype.setAttribute = setAttribute;
}
@@ -189,7 +202,11 @@ describe('when Trusted Types are available in global object', () => {
expect(setAttributeNSCalls[0][1]).toBe('http://www.w3.org/1999/xlink');
expect(setAttributeNSCalls[0][2]).toBe('xlink:href');
// Ensure it didn't get stringified when passed to a DOM sink:
- expect(setAttributeNSCalls[0][3]).toBe(ttObject1);
+ if (gate('enableTrustedTypesIntegration')) {
+ expect(setAttributeNSCalls[0][3]).toBe(ttObject1);
+ } else {
+ expect(setAttributeNSCalls[0][3]).toBe('Hi');
+ }
setAttributeNSCalls.length = 0;
await act(() => {
@@ -201,7 +218,11 @@ describe('when Trusted Types are available in global object', () => {
expect(setAttributeNSCalls[0][1]).toBe('http://www.w3.org/1999/xlink');
expect(setAttributeNSCalls[0][2]).toBe('xlink:href');
// Ensure it didn't get stringified when passed to a DOM sink:
- expect(setAttributeNSCalls[0][3]).toBe(ttObject2);
+ if (gate('enableTrustedTypesIntegration')) {
+ expect(setAttributeNSCalls[0][3]).toBe(ttObject2);
+ } else {
+ expect(setAttributeNSCalls[0][3]).toBe('Bye');
+ }
} finally {
Element.prototype.setAttributeNS = setAttributeNS;
}
@@ -212,13 +233,15 @@ describe('when Trusted Types are available in global object', () => {
await act(() => {
root.render();
});
- assertConsoleErrorDev([
- 'Encountered a script tag while rendering React component. ' +
- 'Scripts inside React components are never executed when rendering ' +
- 'on the client. Consider using template tag instead ' +
- '(https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).\n' +
- ' in script (at **)',
- ]);
+ if (gate('enableTrustedTypesIntegration')) {
+ assertConsoleErrorDev([
+ 'Encountered a script tag while rendering React component. ' +
+ 'Scripts inside React components are never executed when rendering ' +
+ 'on the client. Consider using template tag instead ' +
+ '(https://developer.mozilla.org/en-US/docs/Web/HTML/Element/template).\n' +
+ ' in script (at **)',
+ ]);
+ }
// check that the warning is printed only once
await act(() => {
diff --git a/packages/shared/CheckStringCoercion.js b/packages/shared/CheckStringCoercion.js
index a186d6755d9..0165d8b19c1 100644
--- a/packages/shared/CheckStringCoercion.js
+++ b/packages/shared/CheckStringCoercion.js
@@ -76,6 +76,8 @@ export function checkAttributeStringCoercion(
attributeName: string,
): void | string {
if (__DEV__) {
+ // TODO: for enableTrustedTypesIntegration we don't toString this
+ // so we shouldn't need the DEV warning.
if (willCoercionThrow(value)) {
console.error(
'The provided `%s` attribute is an unsupported type %s.' +
diff --git a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js
index a8d829ee3e3..3a6c456c928 100644
--- a/packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+++ b/packages/shared/forks/ReactFeatureFlags.www-dynamic.js
@@ -35,12 +35,11 @@ export const enableScrollEndPolyfill: boolean = __VARIANT__;
export const enableFragmentRefs: boolean = __VARIANT__;
export const enableFragmentRefsScrollIntoView: boolean = __VARIANT__;
export const enableAsyncDebugInfo: boolean = __VARIANT__;
-
export const enableInternalInstanceMap: boolean = __VARIANT__;
+export const enableTrustedTypesIntegration: boolean = __VARIANT__;
// TODO: These flags are hard-coded to the default values used in open source.
// Update the tests so that they pass in either mode, then set these
// to __VARIANT__.
-export const enableTrustedTypesIntegration: boolean = false;
// You probably *don't* want to add more hardcoded ones.
// Instead, try to add them above with the __VARIANT__ value.
From 875b06489f4436125613535dfe0833cd12a500f9 Mon Sep 17 00:00:00 2001
From: Jack Pope
Date: Wed, 28 Jan 2026 14:45:17 -0500
Subject: [PATCH 3/5] Add text node support to FragmentInstance operations
(#35630)
This PR adds text node support to FragmentInstance operations, allowing
fragment refs to properly handle fragments that contain text nodes
(either mixed with elements or text-only).
Not currently adding/removing new text nodes as we don't need to track
them for events or observers in DOM. Will follow up on this and with
Fabric support.
## Support through parent element
- `dispatchEvent`
- `compareDocumentPosition`
- `getRootNode`
## Support through Range API
- `getClientRects`: Uses Range to calculate bounding rects for text
nodes
- `scrollIntoView`: Uses Range to scroll to text node positions directly
## No support
- `focus`/`focusLast`/`blur`: Noop for text-only fragments
- `observeUsing`: Warns for text-only fragments in DEV
- `addEventListener`/`removeEventListener`: Ignores text nodes, but
still works on Fragment level through `dispatchEvent`
---
.../fragment-refs/GetClientRectsCase.js | 98 ++----
.../PrintRectsFragmentContainer.js | 126 +++++++
.../fixtures/fragment-refs/TextNodesCase.js | 319 ++++++++++++++++++
.../fixtures/fragment-refs/index.js | 2 +
.../src/client/ReactFiberConfigDOM.js | 69 +++-
.../__tests__/ReactDOMFragmentRefs-test.js | 167 +++++++++
.../src/__tests__/utils/IntersectionMocks.js | 55 +++
.../src/ReactFiberTreeReflection.js | 7 +-
packages/shared/ReactFeatureFlags.js | 1 +
.../ReactFeatureFlags.native-fb-dynamic.js | 1 +
.../forks/ReactFeatureFlags.native-fb.js | 1 +
.../forks/ReactFeatureFlags.native-oss.js | 1 +
.../forks/ReactFeatureFlags.test-renderer.js | 1 +
...actFeatureFlags.test-renderer.native-fb.js | 2 +
.../ReactFeatureFlags.test-renderer.www.js | 1 +
.../forks/ReactFeatureFlags.www-dynamic.js | 1 +
.../shared/forks/ReactFeatureFlags.www.js | 1 +
17 files changed, 777 insertions(+), 76 deletions(-)
create mode 100644 fixtures/dom/src/components/fixtures/fragment-refs/PrintRectsFragmentContainer.js
create mode 100644 fixtures/dom/src/components/fixtures/fragment-refs/TextNodesCase.js
diff --git a/fixtures/dom/src/components/fixtures/fragment-refs/GetClientRectsCase.js b/fixtures/dom/src/components/fixtures/fragment-refs/GetClientRectsCase.js
index 563f2ad0542..4d9b2803dde 100644
--- a/fixtures/dom/src/components/fixtures/fragment-refs/GetClientRectsCase.js
+++ b/fixtures/dom/src/components/fixtures/fragment-refs/GetClientRectsCase.js
@@ -1,17 +1,10 @@
import TestCase from '../../TestCase';
import Fixture from '../../Fixture';
+import PrintRectsFragmentContainer from './PrintRectsFragmentContainer';
const React = window.React;
-const {Fragment, useRef, useState} = React;
export default function GetClientRectsCase() {
- const fragmentRef = useRef(null);
- const [rects, setRects] = useState([]);
- const getRects = () => {
- const rects = fragmentRef.current.getClientRects();
- setRects(rects);
- };
-
return (
@@ -26,74 +19,35 @@ export default function GetClientRectsCase() {
-
-
+
+
+ The fragment contains only text nodes. getClientRects should return
+ bounding rectangles for the text content using the Range API.
+
+
+
+
+ This is text content inside a fragment with no element children.
+
+
+
+
+ );
+}
+
+function GetClientRectsMixed() {
+ return (
+
+
+
Click the "Print Rects" button
+
+
+ The fragment contains both text nodes and elements. getClientRects
+ should return bounding rectangles for both text content (via Range API)
+ and elements.
+
+
+
+
+ Text before the span.
+
+ Element
+
+ Text after the span.
+
+ More text at the end.
+
+
+
+
+ );
+}
+
+function FocusTextOnlyNoop() {
+ const fragmentRef = useRef(null);
+ const [message, setMessage] = useState('');
+
+ const tryFocus = () => {
+ fragmentRef.current.focus();
+ setMessage('Called focus() - no-op for text-only fragments');
+ };
+
+ const tryFocusLast = () => {
+ fragmentRef.current.focusLast();
+ setMessage('Called focusLast() - no-op for text-only fragments');
+ };
+
+ return (
+
+
+
Click either focus button
+
+
+ Calling focus() or focusLast() on a fragment with only text children is
+ a no-op. Nothing happens and no warning is logged. This is because text
+ nodes cannot receive focus.
+
+
+
+
+
+ {message && (
+
{message}
+ )}
+
+
+
+ This fragment contains only text. Text nodes are not focusable.
+
+
Scroll down the page so the text fragment is not visible
+
Click one of the scrollIntoView buttons
+
+
+ The page should scroll to bring the text content into view. With
+ alignToTop=true, the text should appear at the top of the viewport. With
+ alignToTop=false, it should appear at the bottom. This uses the Range
+ API to calculate text node positions.
+
+
+
+
+
+ {message && (
+
{message}
+ )}
+
+
+
+ This fragment contains only text. The scrollIntoView method uses the
+ Range API to calculate the text position and scroll to it.
+
+
Scroll down the page so the fragment is not visible
+
Click one of the scrollIntoView buttons
+
+
+ The fragment contains raw text nodes (not wrapped in elements) and
+ elements in alternating order. With alignToTop=true, scroll starts from
+ the last child and works backwards, ending with the first text node at
+ the top. With alignToTop=false, scroll starts from the first child and
+ works forward, ending with the last text node at the bottom. Text nodes
+ use the Range API for scrolling.
+
+
+
+
+
+ {message && (
+
{message}
+ )}
+
+
+
+ TEXT NODE 1 - This is a raw text node at the start of the fragment
+
+ ELEMENT 1
+
+ TEXT NODE 2 - This is a raw text node between elements
+
+ ELEMENT 2
+
+ TEXT NODE 3 - This is a raw text node between elements
+
+ ELEMENT 3
+
+ TEXT NODE 4 - This is a raw text node at the end of the fragment
+
+
+
+
+ A warning should appear in the console because IntersectionObserver
+ cannot observe text nodes. The warning message should indicate that
+ observeUsing() was called on a FragmentInstance with only text children.
+
+
+
+
+ {message && (
+
{message}
+ )}
+
+
+
+ This fragment contains only text. Text nodes cannot be observed.
+
+
No-op (silent): focus, focusLast (text nodes cannot
@@ -310,10 +456,15 @@ export default function TextNodesCase() {
+
+
+
+
+
);
}
diff --git a/fixtures/dom/src/components/fixtures/fragment-refs/index.js b/fixtures/dom/src/components/fixtures/fragment-refs/index.js
index ca6e8185116..91160618c80 100644
--- a/fixtures/dom/src/components/fixtures/fragment-refs/index.js
+++ b/fixtures/dom/src/components/fixtures/fragment-refs/index.js
@@ -5,6 +5,7 @@ import IntersectionObserverCase from './IntersectionObserverCase';
import ResizeObserverCase from './ResizeObserverCase';
import FocusCase from './FocusCase';
import GetClientRectsCase from './GetClientRectsCase';
+import CompareDocumentPositionCase from './CompareDocumentPositionCase';
import ScrollIntoViewCase from './ScrollIntoViewCase';
import TextNodesCase from './TextNodesCase';
@@ -19,6 +20,7 @@ export default function FragmentRefsPage() {
+
From 230772f99dac80be6dda9c59441fb4928612f18e Mon Sep 17 00:00:00 2001
From: Ricky
Date: Wed, 28 Jan 2026 16:00:40 -0500
Subject: [PATCH 5/5] [tests] Fix ReactDOMAttribute-test (#35654)
In https://github.com/facebook/react/pull/35646 I thought there was a
bug in trusted types, but the bug is in jsdom.
For trusted types we still want to check the coersion and throw for a
good dev warning, but prod will also throw becuase the browser will
implicitly coerce to a string. This ensures there's no behavior
difference between dev and prod.
So the right fix is to add in the JSDOM hack that's used in
`ReactDOMSelect-test.js`.
---
.../src/__tests__/ReactDOMAttribute-test.js | 15 ++++++++-------
packages/shared/CheckStringCoercion.js | 2 --
2 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js b/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js
index 63baa38c710..af128d180e8 100644
--- a/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMAttribute-test.js
@@ -9,6 +9,13 @@
'use strict';
+// Fix JSDOM. setAttribute is supposed to throw on things that can't be implicitly toStringed.
+const setAttribute = Element.prototype.setAttribute;
+Element.prototype.setAttribute = function (name, value) {
+ // eslint-disable-next-line react-internal/safe-string-coercion
+ return setAttribute.call(this, name, '' + value);
+};
+
describe('ReactDOM unknown attribute', () => {
let React;
let ReactDOMClient;
@@ -171,13 +178,7 @@ describe('ReactDOM unknown attribute', () => {
const test = () =>
testUnknownAttributeAssignment(new TemporalLike(), null);
- if (gate('enableTrustedTypesIntegration') && !__DEV__) {
- // TODO: this still throws in DEV even though it's not toString'd in prod.
- await expect(test).rejects.toThrowError('2020-01-01');
- } else {
- await expect(test).rejects.toThrowError(new TypeError('prod message'));
- }
-
+ await expect(test).rejects.toThrowError(new TypeError('prod message'));
assertConsoleErrorDev([
'The provided `unknown` attribute is an unsupported type TemporalLike.' +
' This value must be coerced to a string before using it here.\n' +
diff --git a/packages/shared/CheckStringCoercion.js b/packages/shared/CheckStringCoercion.js
index 0165d8b19c1..a186d6755d9 100644
--- a/packages/shared/CheckStringCoercion.js
+++ b/packages/shared/CheckStringCoercion.js
@@ -76,8 +76,6 @@ export function checkAttributeStringCoercion(
attributeName: string,
): void | string {
if (__DEV__) {
- // TODO: for enableTrustedTypesIntegration we don't toString this
- // so we shouldn't need the DEV warning.
if (willCoercionThrow(value)) {
console.error(
'The provided `%s` attribute is an unsupported type %s.' +