) => {
const colorTheme = useColorTheme()
- const canvasIsLive = false
const updateInvalidatedPaths = usePubSubAtomReadOnly(
DomWalkerInvalidatePathsCtxAtom,
AlwaysTrue,
diff --git a/editor/src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-element-renderer-utils.tsx b/editor/src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-element-renderer-utils.tsx
index e23621d57f8c..e8e557494e6d 100644
--- a/editor/src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-element-renderer-utils.tsx
+++ b/editor/src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-element-renderer-utils.tsx
@@ -957,7 +957,7 @@ export function utopiaCanvasJSXLookup(
}
}
-function runJSExpression(
+export function runJSExpression(
block: JSExpression,
elementPath: ElementPath | null,
currentScope: MapLike
,
diff --git a/editor/src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-execution-scope.tsx b/editor/src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-execution-scope.tsx
index f188450fd076..cd40fbbbf6f4 100644
--- a/editor/src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-execution-scope.tsx
+++ b/editor/src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-execution-scope.tsx
@@ -19,7 +19,12 @@ import {
updateMutableUtopiaCtxRefWithNewProps,
UtopiaProjectCtxAtom,
} from './ui-jsx-canvas-contexts'
-import { createLookupRender, utopiaCanvasJSXLookup } from './ui-jsx-canvas-element-renderer-utils'
+import type { RenderContext } from './ui-jsx-canvas-element-renderer-utils'
+import {
+ createLookupRender,
+ runJSExpression,
+ utopiaCanvasJSXLookup,
+} from './ui-jsx-canvas-element-renderer-utils'
import { runBlockUpdatingScope } from './ui-jsx-canvas-scope-utils'
import * as EP from '../../../core/shared/element-path'
import type {
@@ -70,16 +75,6 @@ export function createExecutionScope(
topLevelJsxComponents: Map
requireResult: MapLike
} {
- const filePathMappings = getFilePathMappings(projectContents)
- if (!(filePath in topLevelComponentRendererComponents.current)) {
- // we make sure that the ref has an entry for this filepath
- topLevelComponentRendererComponents.current[filePath] = {}
- }
- let topLevelComponentRendererComponentsForFile =
- topLevelComponentRendererComponents.current[filePath]
-
- const fileBlobsForFile = defaultIfNull(emptyFileBlobs, fileBlobs[filePath])
-
const { topLevelElements, imports, jsxFactoryFunction, combinedTopLevelArbitraryBlock } =
getParseSuccessForFilePath(filePath, projectContents)
const requireResult: MapLike = importResultFromImports(filePath, imports, customRequire)
@@ -105,57 +100,97 @@ export function createExecutionScope(
process: process,
...requireResult,
}
+ const filePathMappings = getFilePathMappings(projectContents)
+ if (!(filePath in topLevelComponentRendererComponents.current)) {
+ // we make sure that the ref has an entry for this filepath
+ topLevelComponentRendererComponents.current[filePath] = {}
+ }
+ let topLevelComponentRendererComponentsForFile =
+ topLevelComponentRendererComponents.current[filePath]
+
+ function addComponentToScope(component: UtopiaJSXComponent): void {
+ const elementName = component.name ?? 'default'
+ topLevelJsxComponents.set(component.name, component)
+ if (elementName in topLevelComponentRendererComponentsForFile) {
+ executionScope[elementName] = topLevelComponentRendererComponentsForFile[elementName]
+ } else {
+ const baseComponent = createComponentRendererComponent({
+ topLevelElementName: component.name,
+ mutableContextRef: mutableContextRef,
+ filePath: filePath,
+ })
+ const wrappedComponent = component.functionWrapping.reduceRight((workingComponent, wrap) => {
+ switch (wrap.type) {
+ case 'SIMPLE_FUNCTION_WRAP':
+ const functionExpression = runJSExpression(
+ wrap.functionExpression,
+ null,
+ executionScope,
+ renderContext,
+ wrap.functionExpression.uid,
+ null,
+ null,
+ )
+ return functionExpression(workingComponent)
+ }
+ }, baseComponent)
+ topLevelComponentRendererComponentsForFile[elementName] = wrappedComponent
+ executionScope[elementName] = wrappedComponent
+ }
+ }
+
+ const fileBlobsForFile = defaultIfNull(emptyFileBlobs, fileBlobs[filePath])
+
// TODO All of this is run on every interaction o_O
let topLevelJsxComponents: Map = new Map()
- // Make sure there is something in scope for all of the top level components
- fastForEach(topLevelElements, (topLevelElement) => {
- if (isUtopiaJSXComponent(topLevelElement)) {
- topLevelJsxComponents.set(topLevelElement.name, topLevelElement)
- const elementName = topLevelElement.name ?? 'default'
- if (!(elementName in topLevelComponentRendererComponentsForFile)) {
- topLevelComponentRendererComponentsForFile[elementName] = createComponentRendererComponent({
- topLevelElementName: topLevelElement.name,
- mutableContextRef: mutableContextRef,
- filePath: filePath,
- })
- }
- }
- })
-
- executionScope = {
- ...executionScope,
- ...topLevelComponentRendererComponentsForFile,
+ const renderContext: RenderContext = {
+ rootScope: executionScope,
+ parentComponentInputProps: {},
+ requireResult: requireResult,
+ hiddenInstances: hiddenInstances,
+ displayNoneInstances: displayNoneInstances,
+ fileBlobs: fileBlobsForFile,
+ validPaths: new Set(),
+ reactChildren: undefined,
+ metadataContext: metadataContext,
+ updateInvalidatedPaths: updateInvalidatedPaths,
+ jsxFactoryFunctionName: jsxFactoryFunction,
+ shouldIncludeCanvasRootInTheSpy: shouldIncludeCanvasRootInTheSpy,
+ filePath: filePath,
+ imports: imports,
+ code: '',
+ highlightBounds: {},
+ editedText: editedText,
+ variablesInScope: {},
+ filePathMappings: filePathMappings,
}
let spiedVariablesInRoot: VariableData = {}
// First make sure everything is in scope
if (combinedTopLevelArbitraryBlock != null && openStoryboardFileNameKILLME != null) {
+ for (const definedElsewhereValue of combinedTopLevelArbitraryBlock.definedElsewhere) {
+ const componentFromThisFile = topLevelElements.find(
+ (topLevelElement): topLevelElement is UtopiaJSXComponent => {
+ return (
+ isUtopiaJSXComponent(topLevelElement) && topLevelElement.name === definedElsewhereValue
+ )
+ },
+ )
+ if (componentFromThisFile != null) {
+ addComponentToScope(componentFromThisFile)
+ }
+ }
const { highlightBounds, code } = getCodeAndHighlightBoundsForFile(filePath, projectContents)
const propertiesFromParams = propertiesExposedByParams(combinedTopLevelArbitraryBlock.params)
const lookupRenderer = createLookupRender(
EP.emptyElementPath,
{
- rootScope: executionScope,
- parentComponentInputProps: {},
- requireResult: requireResult,
- hiddenInstances: hiddenInstances,
- displayNoneInstances: displayNoneInstances,
- fileBlobs: fileBlobsForFile,
- validPaths: new Set(),
- reactChildren: undefined,
- metadataContext: metadataContext,
- updateInvalidatedPaths: updateInvalidatedPaths,
- jsxFactoryFunctionName: jsxFactoryFunction,
- shouldIncludeCanvasRootInTheSpy: shouldIncludeCanvasRootInTheSpy,
- filePath: openStoryboardFileNameKILLME,
- imports: imports,
+ ...renderContext,
code: code,
highlightBounds: highlightBounds,
- editedText: editedText,
- variablesInScope: {},
filePathMappings: filePathMappings,
},
null,
@@ -179,6 +214,14 @@ export function createExecutionScope(
)
spiedVariablesInRoot = spiedVariablesDeclaredWithinBlock
}
+
+ // Make sure there is something in scope for all of the top level components
+ fastForEach(topLevelElements, (topLevelElement) => {
+ if (isUtopiaJSXComponent(topLevelElement)) {
+ addComponentToScope(topLevelElement)
+ }
+ })
+
// WARNING: mutating the mutableContextRef
updateMutableUtopiaCtxRefWithNewProps(mutableContextRef, {
...mutableContextRef.current,
diff --git a/editor/src/components/canvas/ui-jsx-canvas.spec.tsx b/editor/src/components/canvas/ui-jsx-canvas.spec.tsx
index 2c9c1c937373..aac365459d04 100644
--- a/editor/src/components/canvas/ui-jsx-canvas.spec.tsx
+++ b/editor/src/components/canvas/ui-jsx-canvas.spec.tsx
@@ -156,6 +156,91 @@ export { ToBeDefaultExported as default }`,
)
})
+ it('renders a canvas with a component wrapped in React.memo', () => {
+ testCanvasRender(
+ null,
+ `
+ import * as React from 'react'
+ import { Storyboard, Scene } from 'utopia-api'
+
+ export var App = React.memo((props) => {
+ return (
+
+ hi
+
+ )
+ })
+ export var ${BakedInStoryboardVariableName} = (props) => {
+ return (
+
+
+
+
+
+ )
+ }
+ `,
+ )
+ })
+
+ it('renders a canvas with a component wrapped in a function that wraps the component with more content', () => {
+ testCanvasRender(
+ null,
+ `
+ import * as React from 'react'
+ import { Storyboard, Scene } from 'utopia-api'
+
+ function wrapComponent(Component) {
+ return () => {
+ return
+ }
+ }
+
+ export var App = wrapComponent(() => {
+ return (
+
+ hi
+
+ )
+ })
+ export var ${BakedInStoryboardVariableName} = (props) => {
+ return (
+
+
+
+
+
+ )
+ }
+ `,
+ )
+ })
+
it('renders a canvas defined by a utopia storyboard component', () => {
testCanvasRender(
null,
diff --git a/editor/src/components/editor/actions/actions.spec.tsx b/editor/src/components/editor/actions/actions.spec.tsx
index 5bb199009b99..23e68963d126 100644
--- a/editor/src/components/editor/actions/actions.spec.tsx
+++ b/editor/src/components/editor/actions/actions.spec.tsx
@@ -154,6 +154,7 @@ function storyboardComponent(numberOfScenes: number): UtopiaJSXComponent {
false,
'var',
'block',
+ [],
null,
[],
jsxElement(
@@ -186,6 +187,7 @@ const originalModel = deepFreeze(
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
diff --git a/editor/src/components/editor/store/store-deep-equality-instances-2.spec.ts b/editor/src/components/editor/store/store-deep-equality-instances-2.spec.ts
index dab15fd7e549..428d06f16905 100644
--- a/editor/src/components/editor/store/store-deep-equality-instances-2.spec.ts
+++ b/editor/src/components/editor/store/store-deep-equality-instances-2.spec.ts
@@ -788,6 +788,7 @@ describe('UtopiaJSXComponentKeepDeepEquality', () => {
isFunction: true,
declarationSyntax: 'const',
blockOrExpression: 'block',
+ functionWrapping: [],
param: {
type: 'PARAM',
dotDotDotToken: false,
@@ -837,6 +838,7 @@ describe('UtopiaJSXComponentKeepDeepEquality', () => {
isFunction: true,
declarationSyntax: 'const',
blockOrExpression: 'block',
+ functionWrapping: [],
param: {
type: 'PARAM',
dotDotDotToken: false,
@@ -886,6 +888,7 @@ describe('UtopiaJSXComponentKeepDeepEquality', () => {
isFunction: true,
declarationSyntax: 'const',
blockOrExpression: 'block',
+ functionWrapping: [],
param: {
type: 'PARAM',
dotDotDotToken: false,
diff --git a/editor/src/components/editor/store/store-deep-equality-instances.ts b/editor/src/components/editor/store/store-deep-equality-instances.ts
index 90c52ff49778..c2a87be51c3f 100644
--- a/editor/src/components/editor/store/store-deep-equality-instances.ts
+++ b/editor/src/components/editor/store/store-deep-equality-instances.ts
@@ -604,6 +604,8 @@ import type { OnlineState } from '../online-status'
import { onlineState } from '../online-status'
import type { NavigatorRow } from '../../navigator/navigator-row'
import { condensedNavigatorRow, regularNavigatorRow } from '../../navigator/navigator-row'
+import type { SimpleFunctionWrap, FunctionWrap } from 'utopia-shared/src/types'
+import { simpleFunctionWrap, isSimpleFunctionWrap } from 'utopia-shared/src/types'
export function ElementPropertyPathKeepDeepEquality(): KeepDeepEqualityCall {
return combine2EqualityCalls(
@@ -1570,8 +1572,29 @@ export function JSXElementChildKeepDeepEquality(): KeepDeepEqualityCall =
+ combine1EqualityCall(
+ (wrap) => wrap.functionExpression,
+ JSExpressionKeepDeepEqualityCall,
+ simpleFunctionWrap,
+ )
+
+export const FunctionWrapKeepDeepEquality: KeepDeepEqualityCall = (
+ oldValue,
+ newValue,
+) => {
+ switch (oldValue.type) {
+ case 'SIMPLE_FUNCTION_WRAP':
+ if (isSimpleFunctionWrap(newValue)) {
+ return SimpleFunctionWrapKeepDeepEquality(oldValue, newValue)
+ }
+ break
+ }
+ return keepDeepEqualityResult(newValue, false)
+}
+
export const UtopiaJSXComponentKeepDeepEquality: KeepDeepEqualityCall =
- combine10EqualityCalls(
+ combine11EqualityCalls(
(component) => component.name,
createCallWithTripleEquals(),
(component) => component.isFunction,
@@ -1580,6 +1603,8 @@ export const UtopiaJSXComponentKeepDeepEquality: KeepDeepEqualityCall component.blockOrExpression,
createCallWithTripleEquals(),
+ (component) => component.functionWrapping,
+ arrayDeepEquality(FunctionWrapKeepDeepEquality),
(component) => component.param,
nullableDeepEquality(ParamKeepDeepEquality()),
(component) => component.propsUsed,
diff --git a/editor/src/core/model/__snapshots__/storyboard-utils.spec.ts.snap b/editor/src/core/model/__snapshots__/storyboard-utils.spec.ts.snap
index 9d067e4dce25..e1545668b90d 100644
--- a/editor/src/core/model/__snapshots__/storyboard-utils.spec.ts.snap
+++ b/editor/src/core/model/__snapshots__/storyboard-utils.spec.ts.snap
@@ -12,6 +12,7 @@ Array [
"arbitraryJSBlock": null,
"blockOrExpression": "block",
"declarationSyntax": "var",
+ "functionWrapping": Array [],
"isFunction": false,
"name": "storyboard",
"param": null,
diff --git a/editor/src/core/model/element-template-utils.spec.tsx b/editor/src/core/model/element-template-utils.spec.tsx
index a7e46db78aa7..b37102c9fdeb 100644
--- a/editor/src/core/model/element-template-utils.spec.tsx
+++ b/editor/src/core/model/element-template-utils.spec.tsx
@@ -192,6 +192,7 @@ describe('removeJSXElementChild', () => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -212,6 +213,7 @@ describe('removeJSXElementChild', () => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
diff --git a/editor/src/core/model/scene-utils.ts b/editor/src/core/model/scene-utils.ts
index b10dacf9193a..fbbe93583339 100644
--- a/editor/src/core/model/scene-utils.ts
+++ b/editor/src/core/model/scene-utils.ts
@@ -116,6 +116,7 @@ export function convertScenesToUtopiaCanvasComponent(
false,
'var',
'block',
+ [],
null,
[],
jsxElement(
diff --git a/editor/src/core/model/storyboard-utils.ts b/editor/src/core/model/storyboard-utils.ts
index 46211943d6d7..c8bf0942475e 100644
--- a/editor/src/core/model/storyboard-utils.ts
+++ b/editor/src/core/model/storyboard-utils.ts
@@ -384,6 +384,7 @@ function addStoryboardFileForComponent(
false,
'var',
'block',
+ [],
null,
[],
storyboardElement,
diff --git a/editor/src/core/model/test-ui-js-file.test-utils.ts b/editor/src/core/model/test-ui-js-file.test-utils.ts
index 6829a626dc91..9bdb51c0b620 100644
--- a/editor/src/core/model/test-ui-js-file.test-utils.ts
+++ b/editor/src/core/model/test-ui-js-file.test-utils.ts
@@ -61,6 +61,7 @@ const mainComponentForTests = utopiaJSXComponent(
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -332,6 +333,7 @@ const scene = utopiaJSXComponent(
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -398,6 +400,7 @@ const TestStoryboard = utopiaJSXComponent(
false,
'var',
'block',
+ [],
null,
[],
jsxElement(
diff --git a/editor/src/core/property-controls/property-controls-local.ts b/editor/src/core/property-controls/property-controls-local.ts
index f688a5f9378b..340f7cf073b3 100644
--- a/editor/src/core/property-controls/property-controls-local.ts
+++ b/editor/src/core/property-controls/property-controls-local.ts
@@ -117,6 +117,7 @@ import type { ScriptLine } from '../../third-party/react-error-overlay/utils/sta
import { intrinsicHTMLElementNamesAsStrings } from '../shared/dom-utils'
import { valueOrArrayToArray } from '../shared/array-utils'
import { optionalMap } from '../shared/optional-utils'
+import type { RenderContext } from 'src/components/canvas/ui-jsx-canvas-renderer/ui-jsx-canvas-element-renderer-utils'
const exportedNameSymbol = Symbol('__utopia__exportedName')
const moduleNameSymbol = Symbol('__utopia__moduleName')
diff --git a/editor/src/core/shared/element-template.ts b/editor/src/core/shared/element-template.ts
index af6d66b0fc11..c52695b3d1bd 100644
--- a/editor/src/core/shared/element-template.ts
+++ b/editor/src/core/shared/element-template.ts
@@ -111,6 +111,7 @@ import type {
HugProperty,
HugPropertyWidthHeight,
ElementsByUID,
+ FunctionWrap,
} from 'utopia-shared/src/types/element-template'
import type { VariableData } from '../../components/canvas/ui-jsx-canvas'
@@ -198,6 +199,7 @@ import {
emptyComments,
emptyComputedStyle,
emptyAttributeMetadata,
+ simpleFunctionWrap,
} from 'utopia-shared/src/types/element-template'
export { emptyComments, emptyComputedStyle, emptyAttributeMetadata }
@@ -1981,6 +1983,7 @@ export function utopiaJSXComponent(
isFunction: boolean,
declarationSyntax: FunctionDeclarationSyntax,
blockOrExpression: BlockOrExpression,
+ functionWrapping: Array,
param: Param | null,
propsUsed: Array,
rootElement: JSXElementChild,
@@ -1994,6 +1997,7 @@ export function utopiaJSXComponent(
isFunction: isFunction,
declarationSyntax: declarationSyntax,
blockOrExpression: blockOrExpression,
+ functionWrapping: functionWrapping,
param: param,
propsUsed: propsUsed,
rootElement: rootElement,
@@ -2179,8 +2183,6 @@ export function propertiesExposedByParam(param: Param): Array {
}
}
-// FIXME we need to inject data-uids using insertDataUIDsIntoCode
-
export function clearArbitraryJSBlockUniqueIDs(block: ArbitraryJSBlock): ArbitraryJSBlock {
return {
...block,
@@ -2302,6 +2304,24 @@ export function clearParamUniqueIDs(param: Param): Param {
return functionParam(param.dotDotDotToken, clearBoundParamUniqueIDs(param.boundParam))
}
+export function clearFunctionWrapUniqueIDs(wrap: FunctionWrap): FunctionWrap {
+ switch (wrap.type) {
+ case 'SIMPLE_FUNCTION_WRAP':
+ return simpleFunctionWrap(clearExpressionUniqueIDs(wrap.functionExpression))
+ }
+}
+
+export function clearFunctionWrapSourceMaps(wrap: FunctionWrap): FunctionWrap {
+ switch (wrap.type) {
+ case 'SIMPLE_FUNCTION_WRAP':
+ return simpleFunctionWrap(clearExpressionSourceMaps(wrap.functionExpression))
+ }
+}
+
+export function clearFunctionWrapUniqueIDsAndSourceMaps(wrap: FunctionWrap): FunctionWrap {
+ return clearFunctionWrapSourceMaps(clearFunctionWrapUniqueIDs(wrap))
+}
+
// FIXME: Should only really be in test code.
export function clearTopLevelElementUniqueIDs(element: UtopiaJSXComponent): UtopiaJSXComponent
export function clearTopLevelElementUniqueIDs(element: ArbitraryJSBlock): ArbitraryJSBlock
@@ -2312,6 +2332,7 @@ export function clearTopLevelElementUniqueIDs(element: TopLevelElement): TopLeve
let updatedComponent: UtopiaJSXComponent = {
...element,
rootElement: clearJSXElementChildUniqueIDs(element.rootElement),
+ functionWrapping: element.functionWrapping.map(clearFunctionWrapUniqueIDsAndSourceMaps),
}
if (updatedComponent.arbitraryJSBlock != null) {
updatedComponent.arbitraryJSBlock = clearArbitraryJSBlockUniqueIDs(
diff --git a/editor/src/core/workers/parser-printer/__snapshots__/parser-printer-arbitrary-elements.spec.tsx.snap b/editor/src/core/workers/parser-printer/__snapshots__/parser-printer-arbitrary-elements.spec.tsx.snap
index 07cd2a31dd54..6c9a32b345b4 100644
--- a/editor/src/core/workers/parser-printer/__snapshots__/parser-printer-arbitrary-elements.spec.tsx.snap
+++ b/editor/src/core/workers/parser-printer/__snapshots__/parser-printer-arbitrary-elements.spec.tsx.snap
@@ -603,6 +603,7 @@ export var storyboard = (
"arbitraryJSBlock": null,
"blockOrExpression": "block",
"declarationSyntax": "var",
+ "functionWrapping": Array [],
"isFunction": true,
"name": "App",
"param": Object {
@@ -987,6 +988,7 @@ export var storyboard = (
"arbitraryJSBlock": null,
"blockOrExpression": "parenthesized-expression",
"declarationSyntax": "var",
+ "functionWrapping": Array [],
"isFunction": false,
"name": "storyboard",
"param": null,
diff --git a/editor/src/core/workers/parser-printer/__snapshots__/parser-printer-dot-notation.spec.tsx.snap b/editor/src/core/workers/parser-printer/__snapshots__/parser-printer-dot-notation.spec.tsx.snap
index f89abc4ca6a5..02d5c59eea75 100644
--- a/editor/src/core/workers/parser-printer/__snapshots__/parser-printer-dot-notation.spec.tsx.snap
+++ b/editor/src/core/workers/parser-printer/__snapshots__/parser-printer-dot-notation.spec.tsx.snap
@@ -48,6 +48,7 @@ Array [
"arbitraryJSBlock": null,
"blockOrExpression": "block",
"declarationSyntax": "var",
+ "functionWrapping": Array [],
"isFunction": true,
"name": "App",
"param": Object {
diff --git a/editor/src/core/workers/parser-printer/__snapshots__/parser-printer.spec.ts.snap b/editor/src/core/workers/parser-printer/__snapshots__/parser-printer.spec.ts.snap
index eeae8f05557a..24d0718487bd 100644
--- a/editor/src/core/workers/parser-printer/__snapshots__/parser-printer.spec.ts.snap
+++ b/editor/src/core/workers/parser-printer/__snapshots__/parser-printer.spec.ts.snap
@@ -566,6 +566,7 @@ export var whatever = (props) => {
"arbitraryJSBlock": null,
"blockOrExpression": "block",
"declarationSyntax": "var",
+ "functionWrapping": Array [],
"isFunction": true,
"name": "whatever",
"param": Object {
@@ -952,6 +953,7 @@ Object {
"arbitraryJSBlock": null,
"blockOrExpression": "expression",
"declarationSyntax": "const",
+ "functionWrapping": Array [],
"isFunction": true,
"name": "a",
"param": Object {
@@ -1108,6 +1110,7 @@ const b = (n) => n > 0 ? n
: a(10)
"arbitraryJSBlock": null,
"blockOrExpression": "expression",
"declarationSyntax": "var",
+ "functionWrapping": Array [],
"isFunction": true,
"name": "whatever",
"param": Object {
@@ -1166,6 +1169,7 @@ const b = (n) => n > 0 ? n
: a(10)
"arbitraryJSBlock": null,
"blockOrExpression": "expression",
"declarationSyntax": "const",
+ "functionWrapping": Array [],
"isFunction": true,
"name": "b",
"param": Object {
@@ -1656,6 +1660,7 @@ export var App = (props) =>
"arbitraryJSBlock": null,
"blockOrExpression": "expression",
"declarationSyntax": "var",
+ "functionWrapping": Array [],
"isFunction": true,
"name": "App",
"param": Object {
diff --git a/editor/src/core/workers/parser-printer/parser-printer-arbitrary-elements.spec.tsx b/editor/src/core/workers/parser-printer/parser-printer-arbitrary-elements.spec.tsx
index 46df412fa758..6392ee2f2409 100644
--- a/editor/src/core/workers/parser-printer/parser-printer-arbitrary-elements.spec.tsx
+++ b/editor/src/core/workers/parser-printer/parser-printer-arbitrary-elements.spec.tsx
@@ -320,6 +320,7 @@ export var whatever = props => (
true,
'var',
'expression',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -367,6 +368,7 @@ export var whatever = props => (
true,
'var',
'parenthesized-expression',
+ [],
defaultPropsParam,
[],
view,
@@ -507,6 +509,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -670,6 +673,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -817,6 +821,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -901,6 +906,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -1013,6 +1019,7 @@ return { arr: arr };`
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -1097,6 +1104,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -1207,6 +1215,7 @@ return { arr: arr };`
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -1466,6 +1475,7 @@ export var storyboard = (
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
rootElement,
diff --git a/editor/src/core/workers/parser-printer/parser-printer-function-wrapped.spec.tsx b/editor/src/core/workers/parser-printer/parser-printer-function-wrapped.spec.tsx
new file mode 100644
index 000000000000..52ce6f129b36
--- /dev/null
+++ b/editor/src/core/workers/parser-printer/parser-printer-function-wrapped.spec.tsx
@@ -0,0 +1,163 @@
+import {
+ isArbitraryJSBlock,
+ type ArbitraryJSBlock,
+ type UtopiaJSXComponent,
+ isUtopiaJSXComponent,
+} from '../../shared/element-template'
+import { isParseSuccess } from '../../shared/project-file-types'
+import { printCode, printCodeOptions } from './parser-printer'
+import {
+ clearParseResultSourceMapsUniqueIDsAndEmptyBlocks,
+ clearParseResultUniqueIDsAndEmptyBlocks,
+ testParseCode,
+} from './parser-printer.test-utils'
+
+function getLoneMainTopLevelElement(code: string): UtopiaJSXComponent | ArbitraryJSBlock {
+ const parseResult = clearParseResultSourceMapsUniqueIDsAndEmptyBlocks(testParseCode(code))
+ if (isParseSuccess(parseResult)) {
+ const topLevelElements = parseResult.topLevelElements
+ const mainTopLevelElements = topLevelElements.filter(
+ (element): element is UtopiaJSXComponent | ArbitraryJSBlock =>
+ isUtopiaJSXComponent(element) || isArbitraryJSBlock(element),
+ )
+ switch (mainTopLevelElements.length) {
+ case 0:
+ throw new Error(`No main top level elements found.`)
+ case 1:
+ return mainTopLevelElements[0]
+ default:
+ throw new Error(`More than one main top level element found: ${topLevelElements}`)
+ }
+ } else {
+ throw new Error(`Not a successful parse: ${parseResult}`)
+ }
+}
+
+function testParsePrintParse(code: string): string {
+ const firstParse = clearParseResultUniqueIDsAndEmptyBlocks(testParseCode(code))
+
+ if (!isParseSuccess(firstParse)) {
+ throw new Error(JSON.stringify(firstParse))
+ }
+
+ const firstAsParseSuccess = firstParse
+
+ const printed = printCode(
+ '/index.js',
+ printCodeOptions(false, true, true),
+ firstAsParseSuccess.imports,
+ firstAsParseSuccess.topLevelElements,
+ firstAsParseSuccess.jsxFactoryFunction,
+ firstAsParseSuccess.exportsDetail,
+ )
+
+ const secondParse = clearParseResultUniqueIDsAndEmptyBlocks(testParseCode(printed))
+
+ if (!isParseSuccess(secondParse)) {
+ throw new Error(JSON.stringify(secondParse))
+ }
+
+ const secondAsParseSuccess = firstParse
+ expect(secondAsParseSuccess.topLevelElements).toEqual(firstAsParseSuccess.topLevelElements)
+
+ return printed
+}
+
+const simpleReactMemoComponentCode = `import React from "react";
+export const Wrapped = React.memo(() => {
+ return
+})`
+
+describe('parseCode', () => {
+ it('simple function component wrapped in a React.memo', () => {
+ const element = getLoneMainTopLevelElement(simpleReactMemoComponentCode)
+ expect(element.type).toEqual('UTOPIA_JSX_COMPONENT')
+ expect(element).toMatchInlineSnapshot(`
+ Object {
+ "arbitraryJSBlock": null,
+ "blockOrExpression": "block",
+ "declarationSyntax": "const",
+ "functionWrapping": Array [
+ Object {
+ "functionExpression": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "onValue": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "name": "React",
+ "sourceMap": null,
+ "type": "JS_IDENTIFIER",
+ "uid": "",
+ },
+ "optionallyChained": "not-optionally-chained",
+ "originalJavascript": "React.memo",
+ "property": "memo",
+ "sourceMap": null,
+ "type": "JS_PROPERTY_ACCESS",
+ "uid": "",
+ },
+ "type": "SIMPLE_FUNCTION_WRAP",
+ },
+ ],
+ "isFunction": true,
+ "name": "Wrapped",
+ "param": null,
+ "propsUsed": Array [],
+ "returnStatementComments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "rootElement": Object {
+ "children": Array [],
+ "name": Object {
+ "baseVariable": "div",
+ "propertyPath": Object {
+ "propertyElements": Array [],
+ },
+ },
+ "props": Array [
+ Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "key": "data-uid",
+ "type": "JSX_ATTRIBUTES_ENTRY",
+ "value": Object {
+ "comments": Object {
+ "leadingComments": Array [],
+ "trailingComments": Array [],
+ },
+ "type": "ATTRIBUTE_VALUE",
+ "uid": "",
+ "value": "wrapped",
+ },
+ },
+ ],
+ "type": "JSX_ELEMENT",
+ "uid": "",
+ },
+ "type": "UTOPIA_JSX_COMPONENT",
+ "usedInReactDOMRender": false,
+ }
+ `)
+ })
+})
+
+describe('printCode', () => {
+ it('simple function component wrapped in a React.memo', () => {
+ const actualResult = testParsePrintParse(simpleReactMemoComponentCode)
+ expect(actualResult).toMatchInlineSnapshot(`
+ "import React from 'react'
+ export const Wrapped = React.memo(() => {
+ return
+ })
+ "
+ `)
+ })
+})
diff --git a/editor/src/core/workers/parser-printer/parser-printer-functional-components.spec.ts b/editor/src/core/workers/parser-printer/parser-printer-functional-components.spec.ts
index 7d09434dd43d..cfc9a8836b59 100644
--- a/editor/src/core/workers/parser-printer/parser-printer-functional-components.spec.ts
+++ b/editor/src/core/workers/parser-printer/parser-printer-functional-components.spec.ts
@@ -196,6 +196,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -251,6 +252,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
propsParam,
[],
view,
@@ -289,6 +291,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
propsParam,
[],
view,
@@ -331,6 +334,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
propsParam,
['prop'],
view,
@@ -388,6 +392,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
propsParam,
['prop'],
view,
@@ -431,6 +436,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
propsParam,
['prop'],
view,
@@ -488,6 +494,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
propsParam,
['prop'],
view,
@@ -532,6 +539,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
propsParam,
['prop'],
view,
@@ -571,6 +579,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
propsParam,
[],
view,
@@ -625,6 +634,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
propsParam,
[],
view,
@@ -668,6 +678,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
propsParam,
[],
view,
@@ -724,6 +735,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
outerDestructuredObject,
['arrayPart'],
view,
@@ -812,6 +824,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
outerDestructuredObject,
['arrayPart'],
view,
@@ -851,6 +864,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -894,6 +908,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -950,6 +965,7 @@ describe('Parsing a function component with props', () => {
true,
'var',
'block',
+ [],
defaultPropsParam,
['showA'],
expect.objectContaining({
diff --git a/editor/src/core/workers/parser-printer/parser-printer-uids.spec.tsx b/editor/src/core/workers/parser-printer/parser-printer-uids.spec.tsx
index 926750fd95f3..e1835f8b11b9 100644
--- a/editor/src/core/workers/parser-printer/parser-printer-uids.spec.tsx
+++ b/editor/src/core/workers/parser-printer/parser-printer-uids.spec.tsx
@@ -410,6 +410,7 @@ export var app = (props) => {
tle.isFunction,
tle.declarationSyntax,
tle.blockOrExpression,
+ tle.functionWrapping,
tle.param,
tle.propsUsed,
updatedRootElement,
diff --git a/editor/src/core/workers/parser-printer/parser-printer.spec.ts b/editor/src/core/workers/parser-printer/parser-printer.spec.ts
index a590ad8db935..97b5d7a120b7 100644
--- a/editor/src/core/workers/parser-printer/parser-printer.spec.ts
+++ b/editor/src/core/workers/parser-printer/parser-printer.spec.ts
@@ -165,6 +165,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['leftOfTheCake'],
view,
@@ -230,6 +231,7 @@ export var whatever = () =>
true,
'var',
'expression',
+ [],
null,
[],
view,
@@ -323,6 +325,7 @@ export function whatever(props) {
true,
'function',
'block',
+ [],
defaultPropsParam,
['leftOfTheCake'],
view,
@@ -392,6 +395,7 @@ export function whatever() {
true,
'function',
'block',
+ [],
null,
[],
view,
@@ -485,6 +489,7 @@ export default function whatever(props) {
true,
'function',
'block',
+ [],
defaultPropsParam,
['leftOfTheCake'],
view,
@@ -554,6 +559,7 @@ export default function whatever() {
true,
'function',
'block',
+ [],
null,
[],
view,
@@ -644,6 +650,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['leftOfTheCake'],
view,
@@ -774,6 +781,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['leftOfTheCake'],
view,
@@ -863,6 +871,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['leftOfTheCake'],
view,
@@ -987,6 +996,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['leftOfTheCake', 'rightOfTheCake'],
view,
@@ -1085,6 +1095,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['leftOfTheCake'],
view,
@@ -1183,6 +1194,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
[],
view,
@@ -1373,6 +1385,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['leftOfTheCake'],
view,
@@ -1524,6 +1537,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['leftOfTheCake'],
view,
@@ -1684,6 +1698,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['leftOfTheCake'],
view,
@@ -1774,6 +1789,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
[],
view,
@@ -1951,6 +1967,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -2058,6 +2075,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -2148,6 +2166,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -2243,6 +2262,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -2340,6 +2360,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -2461,6 +2482,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -2637,6 +2659,7 @@ export var whatever = (props) => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -2708,6 +2731,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
[],
view,
@@ -2815,6 +2839,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
[],
view,
@@ -2898,6 +2923,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
[],
view,
@@ -3029,6 +3055,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['left'],
view,
@@ -3208,6 +3235,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
[],
view,
@@ -3349,6 +3377,7 @@ export var whatever = props => (
true,
'var',
'block',
+ [],
defaultPropsParam,
['layout'],
rootDiv,
@@ -3373,6 +3402,7 @@ export var whatever = props => (
true,
'var',
'parenthesized-expression',
+ [],
defaultPropsParam,
[],
view,
@@ -3514,6 +3544,7 @@ export var whatever = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
['color'],
view,
@@ -3584,6 +3615,7 @@ export var whatever = () =>
true,
'var',
'expression',
+ [],
null,
[],
view,
@@ -3646,6 +3678,7 @@ export var App = (props) =>
true,
'var',
'expression',
+ [],
defaultPropsParam,
[],
view,
@@ -3714,6 +3747,7 @@ export var App = (props) =>
true,
'var',
'parenthesized-expression',
+ [],
null,
[],
view,
@@ -3786,6 +3820,7 @@ export var App = (props) =>
true,
'var',
'parenthesized-expression',
+ [],
defaultPropsParam,
[],
view,
@@ -3967,6 +4002,7 @@ var spacing = 20`
true,
'var',
'parenthesized-expression',
+ [],
null,
[],
view,
@@ -4105,6 +4141,7 @@ export var whatever = props => {
true,
'var',
'parenthesized-expression',
+ [],
defaultPropsParam,
[],
view,
@@ -4172,6 +4209,7 @@ export var whatever = props => {
true,
'var',
'parenthesized-expression',
+ [],
defaultPropsParam,
[],
view,
@@ -4272,6 +4310,7 @@ export var whatever = props => {
true,
'var',
'parenthesized-expression',
+ [],
defaultPropsParam,
['color', 'shadowValue', 'there'],
view,
@@ -4417,6 +4456,7 @@ export var whatever = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -4494,6 +4534,7 @@ export var whatever = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -4556,6 +4597,7 @@ export var whatever = props => {
false,
'var',
'parenthesized-expression',
+ [],
null,
[],
jsxElement(
@@ -4622,6 +4664,7 @@ export var App = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -4709,6 +4752,7 @@ export var App = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -4763,6 +4807,7 @@ export var App = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -4837,6 +4882,7 @@ export var App = props => {
true,
'var',
'parenthesized-expression',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -5054,6 +5100,7 @@ export var App = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
['layout'],
jsxElement(
@@ -5252,6 +5299,7 @@ export var App = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
jsxElement(
@@ -5311,6 +5359,7 @@ export var whatever = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -5361,6 +5410,7 @@ export var whatever = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -5469,6 +5519,7 @@ export var whatever = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -5585,6 +5636,7 @@ export var whatever = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -5713,6 +5765,7 @@ export var whatever = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -5879,6 +5932,7 @@ export var whatever = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
@@ -5916,6 +5970,7 @@ export var whatever = props => {
true,
'var',
'block',
+ [],
defaultPropsParam,
[],
view,
diff --git a/editor/src/core/workers/parser-printer/parser-printer.test-utils.ts b/editor/src/core/workers/parser-printer/parser-printer.test-utils.ts
index cfd9fa9fb1a1..52ef75e7c185 100644
--- a/editor/src/core/workers/parser-printer/parser-printer.test-utils.ts
+++ b/editor/src/core/workers/parser-printer/parser-printer.test-utils.ts
@@ -873,6 +873,7 @@ export function utopiaJSXComponentArbitrary(): Arbitrary {
isFunction,
declarationSyntax,
blockOrExpression,
+ [],
defaultPropsParam,
[],
rootElement,
@@ -1477,6 +1478,7 @@ export function clearTopLevelElementSourceMapsUniqueIDsAndEmptyBlocks(
return clearArbitraryJSBlockSourceMaps({
...withoutUID,
javascript: blockCode,
+ sourceMap: null,
})
}
case 'IMPORT_STATEMENT':
diff --git a/editor/src/core/workers/parser-printer/parser-printer.ts b/editor/src/core/workers/parser-printer/parser-printer.ts
index 2655271ed5b5..011e1a4613c6 100644
--- a/editor/src/core/workers/parser-printer/parser-printer.ts
+++ b/editor/src/core/workers/parser-printer/parser-printer.ts
@@ -109,6 +109,7 @@ import {
markedAsExported,
markedAsDefault,
parseParams,
+ parseAttributeExpression,
} from './parser-printer-parsing'
import { jsonToExpression } from './json-to-expression'
import { difference } from '../../shared/set-utils'
@@ -122,6 +123,8 @@ import type { Optic } from '../../../core/shared/optics/optics'
import { modify } from '../../../core/shared/optics/optic-utilities'
import { identifyValuesDefinedInNode } from './parser-printer-expressions'
import { isParseableFile } from '../../shared/file-utils'
+import type { FunctionWrap } from 'utopia-shared/src/types/element-template'
+import { simpleFunctionWrap } from 'utopia-shared/src/types/element-template'
const BakedInStoryboardVariableName = 'storyboard'
@@ -722,6 +725,25 @@ function getModifersForComponent(
return result
}
+function printFunctionWrapping(
+ printOptions: PrintCodeOptions,
+ imports: Imports,
+ statement: TS.Expression,
+ functionWrapping: Array,
+): TS.Expression {
+ return functionWrapping.reduceRight((workingStatement, wrap) => {
+ switch (wrap.type) {
+ case 'SIMPLE_FUNCTION_WRAP':
+ const functionExpression = jsxAttributeToExpression(
+ wrap.functionExpression,
+ imports,
+ printOptions.stripUIDs,
+ )
+ return TS.createCall(functionExpression, undefined, [workingStatement])
+ }
+ }, statement)
+}
+
function printUtopiaJSXComponent(
printOptions: PrintCodeOptions,
imports: Imports,
@@ -800,10 +822,21 @@ function printUtopiaJSXComponent(
undefined,
bodyForArrowFunction(),
)
+ const wrappedArrowFunction = printFunctionWrapping(
+ printOptions,
+ imports,
+ arrowFunction,
+ element.functionWrapping,
+ )
if (element.name == null) {
- elementNode = TS.createExportAssignment(undefined, modifiers, undefined, arrowFunction)
+ elementNode = TS.createExportAssignment(
+ undefined,
+ modifiers,
+ undefined,
+ wrappedArrowFunction,
+ )
} else {
- const varDec = TS.createVariableDeclaration(element.name, undefined, arrowFunction)
+ const varDec = TS.createVariableDeclaration(element.name, undefined, wrappedArrowFunction)
const varDecList = TS.createVariableDeclarationList([varDec], nodeFlags)
elementNode = TS.createVariableStatement(modifiers, varDecList)
}
@@ -901,10 +934,6 @@ function optionallyCreateDotDotDotToken(createIt: boolean): TS.DotDotDotToken |
return createIt ? TS.createToken(TS.SyntaxKind.DotDotDotToken) : undefined
}
-function printRawComment(comment: Comment): string {
- return `${comment.rawText}${comment.trailingNewLine ? '\n' : ''}`
-}
-
function printArbitraryJSBlock(block: ArbitraryJSBlock): TS.Node {
return TS.createUnparsedSourceFile(block.javascript)
}
@@ -1092,18 +1121,21 @@ interface PossibleCanvasContentsExpression {
name: string
initializer: TS.Expression
declarationSyntax: FunctionDeclarationSyntax
+ functionWrapping: Array
}
function possibleCanvasContentsExpression(
name: string,
initializer: TS.Expression,
declarationSyntax: FunctionDeclarationSyntax,
+ functionWrapping: Array,
): PossibleCanvasContentsExpression {
return {
type: 'POSSIBLE_CANVAS_CONTENTS_EXPRESSION',
name: name,
initializer: initializer,
declarationSyntax: declarationSyntax,
+ functionWrapping: functionWrapping,
}
}
@@ -1113,6 +1145,7 @@ interface PossibleCanvasContentsFunction {
parameters: TS.NodeArray
body: TS.ConciseBody
declarationSyntax: FunctionDeclarationSyntax
+ functionWrapping: Array
}
function possibleCanvasContentsFunction(
@@ -1120,6 +1153,7 @@ function possibleCanvasContentsFunction(
parameters: TS.NodeArray,
body: TS.ConciseBody,
declarationSyntax: FunctionDeclarationSyntax,
+ functionWrapping: Array,
): PossibleCanvasContentsFunction {
return {
type: 'POSSIBLE_CANVAS_CONTENTS_FUNCTION',
@@ -1127,6 +1161,7 @@ function possibleCanvasContentsFunction(
parameters: parameters,
body: body,
declarationSyntax: declarationSyntax,
+ functionWrapping: functionWrapping,
}
}
@@ -1165,37 +1200,77 @@ function nodeFlagsForVarLetOrConst(varLetOrConst: VarLetOrConst): TS.NodeFlags {
export function looksLikeCanvasElements(
sourceFile: TS.SourceFile,
node: TS.Node,
+ sourceText: string,
+ filename: string,
+ imports: Imports,
+ topLevelNames: Array,
+ propsObjectName: string | null,
+ existingHighlightBounds: Readonly,
+ alreadyExistingUIDs: Set,
+ applySteganography: SteganographyMode,
): Either {
if (TS.isVariableStatement(node)) {
const variableDeclaration = node.declarationList.declarations[0]
if (variableDeclaration != null) {
if (variableDeclaration.initializer != null) {
const name = variableDeclaration.name.getText(sourceFile)
- const initializer = variableDeclaration.initializer
const varLetOrConst = getVarLetOrConst(node.declarationList)
- if (TS.isArrowFunction(initializer)) {
- return right(
- possibleCanvasContentsFunction(
- name,
- initializer.parameters,
- initializer.body,
- varLetOrConst,
- ),
- )
- } else if (name === BakedInStoryboardVariableName) {
- // FIXME The below case should result in a parsed top level *element*, but instead it is treated
- // as a *component*. Unfortunately our use of the storyboard incorrectly relies on it being treated
- // as a component. See https://github.com/concrete-utopia/utopia/issues/1718 for more info
- return right(
- possibleCanvasContentsExpression(name, variableDeclaration.initializer, varLetOrConst),
- )
+ const initializer = variableDeclaration.initializer
+
+ function processVariable(
+ currentNode: TS.Node,
+ functionWrapping: Array,
+ ): Either {
+ if (TS.isCallExpression(currentNode)) {
+ const expressionFromCall = parseAttributeExpression(
+ sourceFile,
+ sourceText,
+ filename,
+ imports,
+ topLevelNames,
+ propsObjectName,
+ currentNode.expression,
+ existingHighlightBounds,
+ alreadyExistingUIDs,
+ [],
+ applySteganography,
+ 'outermost-expression',
+ )
+ if (isRight(expressionFromCall) && currentNode.arguments.length === 1) {
+ const newFunctionWrapping = [
+ ...functionWrapping,
+ simpleFunctionWrap(expressionFromCall.value.value),
+ ]
+ return processVariable(currentNode.arguments[0], newFunctionWrapping)
+ }
+ } else if (TS.isArrowFunction(currentNode)) {
+ return right(
+ possibleCanvasContentsFunction(
+ name,
+ currentNode.parameters,
+ currentNode.body,
+ varLetOrConst,
+ functionWrapping,
+ ),
+ )
+ } else if (name === BakedInStoryboardVariableName) {
+ // FIXME The below case should result in a parsed top level *element*, but instead it is treated
+ // as a *component*. Unfortunately our use of the storyboard incorrectly relies on it being treated
+ // as a component. See https://github.com/concrete-utopia/utopia/issues/1718 for more info
+ return right(
+ possibleCanvasContentsExpression(name, initializer, varLetOrConst, functionWrapping),
+ )
+ }
+ return left(node)
}
+
+ return processVariable(initializer, [])
}
}
} else if (TS.isFunctionDeclaration(node)) {
if (node.body != null) {
const name = node.name == null ? null : node.name.getText(sourceFile)
- return right(possibleCanvasContentsFunction(name, node.parameters, node.body, 'function'))
+ return right(possibleCanvasContentsFunction(name, node.parameters, node.body, 'function', []))
}
}
@@ -1537,7 +1612,18 @@ export function parseCode(
pushImportStatement(rawImportStatement)
}
} else {
- const possibleDeclaration = looksLikeCanvasElements(sourceFile, topLevelElement)
+ const possibleDeclaration = looksLikeCanvasElements(
+ sourceFile,
+ topLevelElement,
+ sourceText,
+ filePath,
+ imports,
+ topLevelNames,
+ null,
+ highlightBounds,
+ alreadyExistingUIDs_MUTABLE,
+ applySteganography,
+ )
if (isRight(possibleDeclaration)) {
const canvasContents = possibleDeclaration.value
const { name } = canvasContents
@@ -1658,6 +1744,7 @@ export function parseCode(
isFunction,
canvasContents.declarationSyntax,
contents.blockOrExpression,
+ canvasContents.functionWrapping,
foldEither(
(_) => null,
(param) => param?.value ?? null,
@@ -1799,6 +1886,7 @@ export function parseCode(
topLevelElement.isFunction,
topLevelElement.declarationSyntax,
topLevelElement.blockOrExpression,
+ topLevelElement.functionWrapping,
topLevelElement.param,
topLevelElement.propsUsed,
topLevelElement.rootElement,
diff --git a/editor/src/core/workers/ts/ts-worker.spec.ts b/editor/src/core/workers/ts/ts-worker.spec.ts
index 6a7007598f83..bef9006c3b66 100644
--- a/editor/src/core/workers/ts/ts-worker.spec.ts
+++ b/editor/src/core/workers/ts/ts-worker.spec.ts
@@ -153,6 +153,7 @@ const SampleInitTSWorkerMessage: IncomingWorkerMessage = {
isFunction: true,
declarationSyntax: 'var',
blockOrExpression: 'block',
+ functionWrapping: [],
param: {
type: 'PARAM',
dotDotDotToken: false,
diff --git a/utopia-shared/src/types/element-template.ts b/utopia-shared/src/types/element-template.ts
index 5adf0419099b..01dd7d49e3ff 100644
--- a/utopia-shared/src/types/element-template.ts
+++ b/utopia-shared/src/types/element-template.ts
@@ -320,6 +320,24 @@ export type VarLetOrConst = 'var' | 'let' | 'const'
export type FunctionDeclarationSyntax = 'function' | VarLetOrConst
export type BlockOrExpression = 'block' | 'parenthesized-expression' | 'expression'
+export interface SimpleFunctionWrap {
+ type: 'SIMPLE_FUNCTION_WRAP'
+ functionExpression: JSExpression
+}
+
+export function simpleFunctionWrap(functionExpression: JSExpression): SimpleFunctionWrap {
+ return {
+ type: 'SIMPLE_FUNCTION_WRAP',
+ functionExpression: functionExpression,
+ }
+}
+
+export type FunctionWrap = SimpleFunctionWrap
+
+export function isSimpleFunctionWrap(wrap: FunctionWrap): wrap is SimpleFunctionWrap {
+ return wrap.type === 'SIMPLE_FUNCTION_WRAP'
+}
+
export interface UtopiaJSXComponent {
type: 'UTOPIA_JSX_COMPONENT'
name: string | null
@@ -331,6 +349,7 @@ export interface UtopiaJSXComponent {
isFunction: boolean
declarationSyntax: FunctionDeclarationSyntax
blockOrExpression: BlockOrExpression
+ functionWrapping: Array
param: Param | null
propsUsed: Array
rootElement: JSXElementChild