Skip to content

Commit

Permalink
feat(editor) Support parsing components with top level if statements
Browse files Browse the repository at this point in the history
  • Loading branch information
Rheeseyb committed Jan 5, 2024
1 parent 5f4ade7 commit 69b4c7f
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 80 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46931,8 +46931,8 @@ exports[`UiJsxCanvas render renders fine with two components that reference each
data-uid=\\"scene\\"
>
<div
data-uid=\\"aaa-unparsed-no-template-path\\"
data-path=\\"utopia-storyboard-uid/scene/app-entity:BBB\\"
data-uid=\\"aaa-root-true~~~1\\"
data-path=\\"utopia-storyboard-uid/scene/app-entity:BBB:bbb-root/bbb-root-false~~~1:aaa-root/aaa-root-false~~~1:bbb-root/bbb-root-false~~~1:aaa-root/aaa-root-false~~~1:bbb-root/bbb-root-false~~~1:aaa-root/aaa-root-true~~~1\\"
>
great
</div>
Expand Down
10 changes: 6 additions & 4 deletions editor/src/components/canvas/ui-jsx-canvas.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1565,18 +1565,20 @@ import Utopia, {
} from 'utopia-api'
export var A = (props) => {
// @utopia/uid=aaa-root
if (props.x === 0) {
return <div>great</div>
return <div data-uid='aaa-root-true'>great</div>
} else {
return <B data-uid={'bbb-unparsed-no-template-path'} x={props.x - 1} />
return <B data-uid='aaa-root-false' x={props.x - 1} />
}
}
export var B = (props) => {
// @utopia/uid=bbb-root
if (props.x === 0) {
return <div>great</div>
return <div data-uid='bbb-root-true'>great</div>
} else {
return <A data-uid={'aaa-unparsed-no-template-path'} x={props.x - 1} />
return <A data-uid='bbb-root-false' x={props.x - 1} />
}
}
Expand Down
14 changes: 14 additions & 0 deletions editor/src/core/shared/element-template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1323,6 +1323,20 @@ export type JSXElementChild =
| JSXFragment
| JSXConditionalExpression

export function canBeRootElementOfComponent(element: JSXElementChild): boolean {
if (isJSXElement(element) || isJSXFragment(element) || isJSXConditionalExpression(element)) {
return true
}

if (isJSExpression(element)) {
if (hasElementsWithin(element)) {
return Object.keys(element.elementsWithin).length > 0
}
}

return false
}

export function isJSXElement(element: JSXElementChild): element is JSXElement {
return element.type === 'JSX_ELEMENT'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ import { GithubPicker } from "react-color";
function Picker() {
const [color, setColor] = useThemeContext();
const [visible, setVisible] = usePickerVisibilityContext();
if (visible) {
return <GithubPicker
style={{ position: "absolute" }}
Expand Down
69 changes: 59 additions & 10 deletions editor/src/core/workers/parser-printer/parser-printer-parsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1053,7 +1053,7 @@ export function parseAttributeOtherJavaScript(
(code, _, definedElsewhere, fileSourceNode, parsedElementsWithin, isList) => {
const { code: codeFromFile, map } = fileSourceNode.toStringWithSourceMap({ file: filename })
const rawMap = JSON.parse(map.toString())
const transpileEither = transpileJavascriptFromCode(
const firstTraspileEither = transpileJavascriptFromCode(
sourceFile.fileName,
sourceFile.text,
codeFromFile,
Expand All @@ -1062,6 +1062,21 @@ export function parseAttributeOtherJavaScript(
true,
applySteganography,
)
const transpileEither = foldEither(
() =>
transpileJavascriptFromCode(
sourceFile.fileName,
sourceFile.text,
codeFromFile,
rawMap,
parsedElementsWithin,
false,
applySteganography,
true,
),
(success) => right(success),
firstTraspileEither,
)
return mapEither((transpileResult) => {
const prependedWithReturn = prependToSourceString(
sourceFile.fileName,
Expand Down Expand Up @@ -3047,15 +3062,49 @@ export function parseOutFunctionContents(
alreadyExistingUIDs,
applySteganography,
)
return mapEither((parsed) => {
highlightBounds = mergeHighlightBounds(highlightBounds, parsed.highlightBounds)
return withParserMetadata(
functionContents(jsBlock, 'block', parsed.value, returnStatementComments),
highlightBounds,
propsUsed.concat(parsed.propsUsed),
definedElsewhere.concat(parsed.definedElsewhere),
)
}, parsedElements)
return foldEither(
() => {
const parsedAsArbitrary = parseAttributeOtherJavaScript(
sourceFile,
sourceText,
filename,
imports,
topLevelNames,
propsObjectName,
possibleElement,
highlightBounds,
alreadyExistingUIDs,
applySteganography,
)

return bimapEither(
(failure) => failure,
(success) => {
highlightBounds = mergeHighlightBounds(highlightBounds, success.highlightBounds)
const elem = successfullyParsedElement(sourceFile, possibleElement, success.value)
return withParserMetadata(
functionContents(jsBlock, 'block', [elem], returnStatementComments),
highlightBounds,
propsUsed.concat(success.propsUsed),
definedElsewhere.concat(success.definedElsewhere),
)
},
parsedAsArbitrary,
)
},
(parsed) => {
highlightBounds = mergeHighlightBounds(highlightBounds, parsed.highlightBounds)
return right(
withParserMetadata(
functionContents(jsBlock, 'block', parsed.value, returnStatementComments),
highlightBounds,
propsUsed.concat(parsed.propsUsed),
definedElsewhere.concat(parsed.definedElsewhere),
),
)
},
parsedElements,
)
}
} else {
const parsedElements = parseOutJSXElements(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,11 @@ import { fastForEach } from '../../shared/utils'
import type { RawSourceMap } from '../ts/ts-typings/RawSourceMap'
import infiniteLoopPrevention from './transform-prevent-infinite-loops'
import type { ElementsWithinInPosition, CodeWithMap } from './parser-printer-utils'
import { wrapCodeInParens, wrapCodeInParensWithMap } from './parser-printer-utils'
import {
wrapCodeInAnonFunctionWithMap,
wrapCodeInParens,
wrapCodeInParensWithMap,
} from './parser-printer-utils'
import { JSX_CANVAS_LOOKUP_FUNCTION_NAME } from '../../shared/dom-utils'
import type { SteganoTextData } from '../../shared/stegano-text'
import { cleanSteganoTextData, encodeSteganoData } from '../../shared/stegano-text'
Expand Down Expand Up @@ -317,6 +321,7 @@ export function transpileJavascriptFromCode(
elementsWithin: ElementsWithinInPosition,
wrapInParens: boolean,
applySteganography: SteganographyMode,
wrapInAnonFunction: boolean = false,
): Either<string, TranspileResult> {
try {
let codeToUse: string = code
Expand All @@ -331,6 +336,15 @@ export function transpileJavascriptFromCode(
)
codeToUse = wrappedInParens.code
mapToUse = wrappedInParens.sourceMap
} else if (wrapInAnonFunction) {
const wrappedInAnonFunction = wrapCodeInAnonFunctionWithMap(
sourceFileName,
sourceFileText,
codeToUse,
mapToUse,
)
codeToUse = wrappedInAnonFunction.code
mapToUse = wrappedInAnonFunction.sourceMap
}

let plugins: Array<any> =
Expand Down
19 changes: 19 additions & 0 deletions editor/src/core/workers/parser-printer/parser-printer-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,25 @@ export function wrapCodeInParensWithMap(
return { code: result.code, sourceMap: result.map }
}

function wrapCodeInAnonFunction(code: string): string {
return `(() => {${removeTrailingSemicolon(code)}})()`
}

export function wrapCodeInAnonFunctionWithMap(
sourceFileName: string,
sourceFileText: string,
code: string,
sourceMap: RawSourceMap,
): CodeWithMap {
const wrappedCode = wrapCodeInAnonFunction(code)

const consumer = new SourceMapConsumer(sourceMap)
const node = SourceNode.fromStringWithSourceMap(wrappedCode, consumer)
node.setSourceContent(sourceFileName, sourceFileText)
const result = node.toStringWithSourceMap({ file: sourceFileName })
return { code: result.code, sourceMap: result.map }
}

export function prependToSourceString(
sourceFileName: string,
sourceFileText: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,6 @@ export function testParseCode(
fastForEach(success.topLevelElements, (topLevelElement) => {
if (isUtopiaJSXComponent(topLevelElement)) {
ensureElementsHaveUID(topLevelElement.rootElement, uids, () => true, 'walk-attributes')
ensureArbitraryJSXBlockCodeHasUIDs(topLevelElement.rootElement)
}
})
}, result)
Expand Down Expand Up @@ -1196,26 +1195,6 @@ function babelCheckForDataUID(): { visitor: BabelTraverse.Visitor } {
}
}

export function ensureArbitraryJSXBlockCodeHasUIDs(jsxElementChild: JSXElementChild): void {
walkElements(
jsxElementChild,
'do-not-include-data-uid-attribute',
(element) => {
if (isJSExpressionMapOrOtherJavaScript(element)) {
const plugins: Array<any> = [ReactSyntaxPlugin, babelCheckForDataUID]

Babel.transform(element.javascript, {
presets: [],
plugins: plugins,
sourceType: 'script',
})
}
},
() => true,
'walk-attributes',
)
}

export interface ArbitraryProject {
code: string
parsed: ParsedTextFile
Expand Down
Loading

0 comments on commit 69b4c7f

Please sign in to comment.