From 828fbf25dd2072156f751cab426986c9ec1c84e8 Mon Sep 17 00:00:00 2001 From: Aditya Subramanyam Date: Sat, 16 Nov 2024 18:44:12 +0530 Subject: [PATCH 1/6] [playground] Fixes #31331 1) Adds support for 'use memo' and 'use no memo' 2) Cleanup E2E test cases a bit 3) Adds test cases for use memo 4) Added documentation to run test cases --- compiler/apps/playground/README.md | 7 ++ .../{default-input.txt => default-output.txt} | 0 .../page.spec.ts/simple-use-memo-output.txt | 19 ++++ .../{user-input.txt => user-output.txt} | 0 .../playground/__tests__/e2e/page.spec.ts | 56 ++++++++---- .../components/Editor/EditorImpl.tsx | 87 +++++++++++++++---- .../src/Entrypoint/Program.ts | 6 +- .../babel-plugin-react-compiler/src/index.ts | 3 + 8 files changed, 143 insertions(+), 35 deletions(-) rename compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/{default-input.txt => default-output.txt} (100%) create mode 100644 compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/simple-use-memo-output.txt rename compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/{user-input.txt => user-output.txt} (100%) diff --git a/compiler/apps/playground/README.md b/compiler/apps/playground/README.md index 0a2fef224a0b0..7b6d5f729ad96 100644 --- a/compiler/apps/playground/README.md +++ b/compiler/apps/playground/README.md @@ -26,6 +26,13 @@ $ npm run dev $ yarn ``` +## Testing +```sh +# Install playwright browser binaries +$ npx playwright install --with-deps +# Run tests +$ yarn test +``` ## Deployment This project has been deployed using Vercel. Vercel does the exact same thing as we would diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/default-input.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/default-output.txt similarity index 100% rename from compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/default-input.txt rename to compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/default-output.txt diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/simple-use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/simple-use-memo-output.txt new file mode 100644 index 0000000000000..b64206a7dc36b --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/simple-use-memo-output.txt @@ -0,0 +1,19 @@ +function useFoo(props) { +  "use no memo"; +  const x = () => {}; +  const y = function (a) {}; +  return foo(props.x, x); +} +function Component() { +  const $ = _c(2); +  const x = useFoo(); +  let t0; +  if ($[0] !== x) { +    t0 = 
{x}
; +    $[0] = x; +    $[1] = t0; +  } else { +    t0 = $[1]; +  } +  return t0; +} \ No newline at end of file diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/user-input.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/user-output.txt similarity index 100% rename from compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/user-input.txt rename to compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/user-output.txt diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts index bc6083f52dc00..24ab78218894f 100644 --- a/compiler/apps/playground/__tests__/e2e/page.spec.ts +++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts @@ -8,42 +8,68 @@ import {expect, test} from '@playwright/test'; import {encodeStore, type Store} from '../../lib/stores'; -const STORE: Store = { - source: `export default function TestComponent({ x }) { - return ; -} -`, -}; -const HASH = encodeStore(STORE); - function concat(data: Array): string { return data.join(''); } -test('editor should compile successfully', async ({page}) => { - await page.goto(`/#${HASH}`, {waitUntil: 'networkidle'}); +test('editor should open successfully', async ({page}) => { + await page.goto(`/`, {waitUntil: 'networkidle'}); await page.screenshot({ fullPage: true, - path: 'test-results/00-on-networkidle.png', + path: 'test-results/00-fresh-page.png', }); +}); +test('editor should compile from hash successfully', async ({page}) => { + const STORE: Store = { + source: `export default function TestComponent({ x }) { + return ; + } + `, + }; + const HASH = encodeStore(STORE); + await page.goto(`/#${HASH}`, {waitUntil: 'networkidle'}); // User input from hash compiles await page.screenshot({ fullPage: true, - path: 'test-results/01-show-js-before.png', + path: 'test-results/01-compiles-from-hash.png', }); const userInput = (await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? []; - expect(concat(userInput)).toMatchSnapshot('user-input.txt'); + expect(concat(userInput)).toMatchSnapshot('user-output.txt'); +}); +test('reset button works', async ({page}) => { + const STORE: Store = { + source: `export default function TestComponent({ x }) { + return ; + } + `, + }; + const HASH = encodeStore(STORE); + await page.goto(`/#${HASH}`, {waitUntil: 'networkidle'}); // Reset button works page.on('dialog', dialog => dialog.accept()); await page.getByRole('button', {name: 'Reset'}).click(); await page.screenshot({ fullPage: true, - path: 'test-results/02-show-js-after.png', + path: 'test-results/02-reset-button-works.png', }); const defaultInput = (await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? []; - expect(concat(defaultInput)).toMatchSnapshot('default-input.txt'); + expect(concat(defaultInput)).toMatchSnapshot('default-output.txt'); }); +test('directives work', async ({page}) => { + await page.goto(`/`, {waitUntil: 'networkidle'}); + + const monacoEditor = page.locator(".monaco-editor").nth(0); + await monacoEditor.click(); + await page.keyboard.press("Meta+A"); + await page.keyboard.press('Backspace'); + await page.keyboard.type(`function useFoo(props) {"use no memo";const x = () => { };const y = function (a) { };return foo(props.x, x);};`); + await page.keyboard.type(`function Component() {const x = useFoo();return
{x}
;}`); + await page.screenshot({ fullPage: true, path: 'test-results/03-simple-use-memo.png' }); + + const useMemoOutput = (await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? []; + expect(concat(useMemoOutput)).toMatchSnapshot('simple-use-memo-output.txt'); +}); \ No newline at end of file diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index 1bdd372ad2b71..0c69e68eff5b2 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -18,6 +18,8 @@ import { ValueKind, runPlayground, type Hook, + findDirectiveDisablingMemoization, + findDirectiveEnablingMemoization, } from 'babel-plugin-react-compiler/src'; import {type ReactFunctionType} from 'babel-plugin-react-compiler/src/HIR/Environment'; import clsx from 'clsx'; @@ -43,6 +45,12 @@ import { import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR'; import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction'; +type FunctionLike = NodePath | NodePath | NodePath; +enum MemoizeDirectiveState { + Enabled, + Disabled, + Undefined, +} function parseInput(input: string, language: 'flow' | 'typescript'): any { // Extract the first line to quickly check for custom test directives if (language === 'flow') { @@ -59,20 +67,23 @@ function parseInput(input: string, language: 'flow' | 'typescript'): any { }); } } - +function checkExplicitMemoizeDirectives(directives: Array): MemoizeDirectiveState { + if(findDirectiveEnablingMemoization(directives).length) { + return MemoizeDirectiveState.Enabled; + } + if(findDirectiveDisablingMemoization(directives).length) { + return MemoizeDirectiveState.Disabled; + } + return MemoizeDirectiveState.Undefined; +} function parseFunctions( source: string, language: 'flow' | 'typescript', -): Array< - | NodePath - | NodePath - | NodePath -> { - const items: Array< - | NodePath - | NodePath - | NodePath - > = []; +): Array<{ + compile: boolean; + fn: FunctionLike; +}> { + const items: Array = []; try { const ast = parseInput(source, language); traverse(ast, { @@ -98,7 +109,33 @@ function parseFunctions( suggestions: null, }); } - return items; + + return items.map<{ + compile: boolean; + fn: FunctionLike; + }>((fn) => { + const { body } = fn.node; + if (t.isBlockStatement(body)) { + const selfCheck = checkExplicitMemoizeDirectives(body.directives); + if (selfCheck === MemoizeDirectiveState.Enabled) return { compile: true, fn }; + if (selfCheck === MemoizeDirectiveState.Disabled) return { compile: false, fn }; + + const parentWithDirective = fn.findParent((parentPath) => { + if (parentPath.isBlockStatement() || parentPath.isProgram()) { + const directiveCheck = checkExplicitMemoizeDirectives(parentPath.node.directives); + return [MemoizeDirectiveState.Enabled, MemoizeDirectiveState.Disabled].includes(directiveCheck); + } + return false; + }); + + if (!parentWithDirective) return { compile: true, fn }; + const parentDirectiveCheck = checkExplicitMemoizeDirectives( + (parentWithDirective.node as t.Program | t.BlockStatement).directives, + ); + return { compile: [MemoizeDirectiveState.Enabled, MemoizeDirectiveState.Undefined].includes(parentDirectiveCheck), fn }; + } + return { compile: false, fn }; + }); } const COMMON_HOOKS: Array<[string, Hook]> = [ @@ -209,18 +246,34 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { // Extract the first line to quickly check for custom test directives const pragma = source.substring(0, source.indexOf('\n')); const config = parseConfigPragmaForTests(pragma); - - for (const fn of parseFunctions(source, language)) { - const id = withIdentifier(getFunctionIdentifier(fn)); + const parsedFunctions = parseFunctions(source, language); + for (const func of parsedFunctions) { + const id = withIdentifier(getFunctionIdentifier(func.fn)); + const fnName = id.name; + if(!func.compile) { + upsert({ + kind: 'ast', + fnName, + name: 'CodeGen', + value: { + type: 'FunctionDeclaration', + id: func.fn.isArrowFunctionExpression() ? null : func.fn.node.id, + async: func.fn.node.async, + generator: !!func.fn.node.generator, + body: func.fn.node.body as t.BlockStatement, + params: func.fn.node.params, + }, + }); + continue; + } for (const result of runPlayground( - fn, + func.fn, { ...config, customHooks: new Map([...COMMON_HOOKS]), }, getReactFunctionType(id), )) { - const fnName = id.name; switch (result.kind) { case 'ast': { upsert({ diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 1b1a82db0b908..f5dbb324bf601 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -42,10 +42,10 @@ export type CompilerPass = { comments: Array; code: string | null; }; -const OPT_IN_DIRECTIVES = new Set(['use forget', 'use memo']); +export const OPT_IN_DIRECTIVES = new Set(['use forget', 'use memo']); export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); -function findDirectiveEnablingMemoization( +export function findDirectiveEnablingMemoization( directives: Array, ): Array { return directives.filter(directive => @@ -53,7 +53,7 @@ function findDirectiveEnablingMemoization( ); } -function findDirectiveDisablingMemoization( +export function findDirectiveDisablingMemoization( directives: Array, ): Array { return directives.filter(directive => diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 60a7e7843ce9b..150df26e45818 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -20,6 +20,9 @@ export { run, runPlayground, OPT_OUT_DIRECTIVES, + OPT_IN_DIRECTIVES, + findDirectiveEnablingMemoization, + findDirectiveDisablingMemoization, type CompilerPipelineValue, type PluginOptions, } from './Entrypoint'; From e0e563d5cea5da0adedd3c380fffe28c463d99ef Mon Sep 17 00:00:00 2001 From: Aditya Subramanyam Date: Sat, 16 Nov 2024 22:31:05 +0530 Subject: [PATCH 2/6] [playground] directive tests somehow fail on CI(probably due to async issue in chromium driver on CI?). Reverted back to hash based compilation --- .../playground/__tests__/e2e/page.spec.ts | 23 ++++++++++++------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts index 24ab78218894f..01ec633a0cda3 100644 --- a/compiler/apps/playground/__tests__/e2e/page.spec.ts +++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts @@ -60,14 +60,21 @@ test('reset button works', async ({page}) => { expect(concat(defaultInput)).toMatchSnapshot('default-output.txt'); }); test('directives work', async ({page}) => { - await page.goto(`/`, {waitUntil: 'networkidle'}); - - const monacoEditor = page.locator(".monaco-editor").nth(0); - await monacoEditor.click(); - await page.keyboard.press("Meta+A"); - await page.keyboard.press('Backspace'); - await page.keyboard.type(`function useFoo(props) {"use no memo";const x = () => { };const y = function (a) { };return foo(props.x, x);};`); - await page.keyboard.type(`function Component() {const x = useFoo();return
{x}
;}`); + const STORE: Store = { + source: `function useFoo(props) { + 'use no memo'; + const x = () => { }; + const y = function (a) { }; + return foo(props.x, x); + } + function Component() { + const x = useFoo(); + return
{x}
; + } + `, + }; + const HASH = encodeStore(STORE); + await page.goto(`/#${HASH}`, {waitUntil: 'networkidle'}); await page.screenshot({ fullPage: true, path: 'test-results/03-simple-use-memo.png' }); const useMemoOutput = (await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? []; From 9b090e4ff6080db287c737cf6495a1b0b2083b1f Mon Sep 17 00:00:00 2001 From: Aditya Subramanyam Date: Sat, 16 Nov 2024 22:36:10 +0530 Subject: [PATCH 3/6] [playground] committing prettier changes --- .../playground/__tests__/e2e/page.spec.ts | 12 +++-- .../components/Editor/EditorImpl.tsx | 50 +++++++++++++------ 2 files changed, 42 insertions(+), 20 deletions(-) diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts index 01ec633a0cda3..a5178d3b00bec 100644 --- a/compiler/apps/playground/__tests__/e2e/page.spec.ts +++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts @@ -74,9 +74,13 @@ test('directives work', async ({page}) => { `, }; const HASH = encodeStore(STORE); - await page.goto(`/#${HASH}`, {waitUntil: 'networkidle'}); - await page.screenshot({ fullPage: true, path: 'test-results/03-simple-use-memo.png' }); + await page.goto(`/#${HASH}`, {waitUntil: 'networkidle'}); + await page.screenshot({ + fullPage: true, + path: 'test-results/03-simple-use-memo.png', + }); - const useMemoOutput = (await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? []; + const useMemoOutput = + (await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? []; expect(concat(useMemoOutput)).toMatchSnapshot('simple-use-memo-output.txt'); -}); \ No newline at end of file +}); diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index 0c69e68eff5b2..b3c7968d0702c 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -45,7 +45,10 @@ import { import {printFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR'; import {printReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction'; -type FunctionLike = NodePath | NodePath | NodePath; +type FunctionLike = + | NodePath + | NodePath + | NodePath; enum MemoizeDirectiveState { Enabled, Disabled, @@ -67,11 +70,13 @@ function parseInput(input: string, language: 'flow' | 'typescript'): any { }); } } -function checkExplicitMemoizeDirectives(directives: Array): MemoizeDirectiveState { - if(findDirectiveEnablingMemoization(directives).length) { +function checkExplicitMemoizeDirectives( + directives: Array, +): MemoizeDirectiveState { + if (findDirectiveEnablingMemoization(directives).length) { return MemoizeDirectiveState.Enabled; } - if(findDirectiveDisablingMemoization(directives).length) { + if (findDirectiveDisablingMemoization(directives).length) { return MemoizeDirectiveState.Disabled; } return MemoizeDirectiveState.Undefined; @@ -113,28 +118,41 @@ function parseFunctions( return items.map<{ compile: boolean; fn: FunctionLike; - }>((fn) => { - const { body } = fn.node; + }>(fn => { + const {body} = fn.node; if (t.isBlockStatement(body)) { const selfCheck = checkExplicitMemoizeDirectives(body.directives); - if (selfCheck === MemoizeDirectiveState.Enabled) return { compile: true, fn }; - if (selfCheck === MemoizeDirectiveState.Disabled) return { compile: false, fn }; + if (selfCheck === MemoizeDirectiveState.Enabled) + return {compile: true, fn}; + if (selfCheck === MemoizeDirectiveState.Disabled) + return {compile: false, fn}; - const parentWithDirective = fn.findParent((parentPath) => { + const parentWithDirective = fn.findParent(parentPath => { if (parentPath.isBlockStatement() || parentPath.isProgram()) { - const directiveCheck = checkExplicitMemoizeDirectives(parentPath.node.directives); - return [MemoizeDirectiveState.Enabled, MemoizeDirectiveState.Disabled].includes(directiveCheck); + const directiveCheck = checkExplicitMemoizeDirectives( + parentPath.node.directives, + ); + return [ + MemoizeDirectiveState.Enabled, + MemoizeDirectiveState.Disabled, + ].includes(directiveCheck); } return false; }); - if (!parentWithDirective) return { compile: true, fn }; + if (!parentWithDirective) return {compile: true, fn}; const parentDirectiveCheck = checkExplicitMemoizeDirectives( (parentWithDirective.node as t.Program | t.BlockStatement).directives, ); - return { compile: [MemoizeDirectiveState.Enabled, MemoizeDirectiveState.Undefined].includes(parentDirectiveCheck), fn }; + return { + compile: [ + MemoizeDirectiveState.Enabled, + MemoizeDirectiveState.Undefined, + ].includes(parentDirectiveCheck), + fn, + }; } - return { compile: false, fn }; + return {compile: false, fn}; }); } @@ -250,14 +268,14 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { for (const func of parsedFunctions) { const id = withIdentifier(getFunctionIdentifier(func.fn)); const fnName = id.name; - if(!func.compile) { + if (!func.compile) { upsert({ kind: 'ast', fnName, name: 'CodeGen', value: { type: 'FunctionDeclaration', - id: func.fn.isArrowFunctionExpression() ? null : func.fn.node.id, + id: func.fn.isArrowFunctionExpression() ? null : func.fn.node.id, async: func.fn.node.async, generator: !!func.fn.node.generator, body: func.fn.node.body as t.BlockStatement, From 21ed271fdcab4b1e6d4045af6f437e6bb733ca13 Mon Sep 17 00:00:00 2001 From: Aditya Subramanyam Date: Sun, 17 Nov 2024 00:32:25 +0530 Subject: [PATCH 4/6] [playground] 1) removing redundant map and doing the compilation check while traversing itself 2) cleanup of checkExplicitMemoizeDirectives to return a string instead 3) compilation flag changed to `compilationEnabled` for accuracy 4) fixing of improperly cased variabled names in e2e test files --- .../playground/__tests__/e2e/page.spec.ts | 18 +-- .../components/Editor/EditorImpl.tsx | 129 ++++++++++-------- 2 files changed, 80 insertions(+), 67 deletions(-) diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts index a5178d3b00bec..f938cbe363b16 100644 --- a/compiler/apps/playground/__tests__/e2e/page.spec.ts +++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts @@ -20,14 +20,14 @@ test('editor should open successfully', async ({page}) => { }); }); test('editor should compile from hash successfully', async ({page}) => { - const STORE: Store = { + const store: Store = { source: `export default function TestComponent({ x }) { return ; } `, }; - const HASH = encodeStore(STORE); - await page.goto(`/#${HASH}`, {waitUntil: 'networkidle'}); + const hash = encodeStore(store); + await page.goto(`/#${hash}`, {waitUntil: 'networkidle'}); // User input from hash compiles await page.screenshot({ @@ -39,14 +39,14 @@ test('editor should compile from hash successfully', async ({page}) => { expect(concat(userInput)).toMatchSnapshot('user-output.txt'); }); test('reset button works', async ({page}) => { - const STORE: Store = { + const store: Store = { source: `export default function TestComponent({ x }) { return ; } `, }; - const HASH = encodeStore(STORE); - await page.goto(`/#${HASH}`, {waitUntil: 'networkidle'}); + const hash = encodeStore(store); + await page.goto(`/#${hash}`, {waitUntil: 'networkidle'}); // Reset button works page.on('dialog', dialog => dialog.accept()); @@ -60,7 +60,7 @@ test('reset button works', async ({page}) => { expect(concat(defaultInput)).toMatchSnapshot('default-output.txt'); }); test('directives work', async ({page}) => { - const STORE: Store = { + const store: Store = { source: `function useFoo(props) { 'use no memo'; const x = () => { }; @@ -73,8 +73,8 @@ test('directives work', async ({page}) => { } `, }; - const HASH = encodeStore(STORE); - await page.goto(`/#${HASH}`, {waitUntil: 'networkidle'}); + const hash = encodeStore(store); + await page.goto(`/#${hash}`, {waitUntil: 'networkidle'}); await page.screenshot({ fullPage: true, path: 'test-results/03-simple-use-memo.png', diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index b3c7968d0702c..68185a6d4c677 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -49,11 +49,7 @@ type FunctionLike = | NodePath | NodePath | NodePath; -enum MemoizeDirectiveState { - Enabled, - Disabled, - Undefined, -} + function parseInput(input: string, language: 'flow' | 'typescript'): any { // Extract the first line to quickly check for custom test directives if (language === 'flow') { @@ -70,38 +66,55 @@ function parseInput(input: string, language: 'flow' | 'typescript'): any { }); } } -function checkExplicitMemoizeDirectives( - directives: Array, -): MemoizeDirectiveState { - if (findDirectiveEnablingMemoization(directives).length) { - return MemoizeDirectiveState.Enabled; - } - if (findDirectiveDisablingMemoization(directives).length) { - return MemoizeDirectiveState.Disabled; - } - return MemoizeDirectiveState.Undefined; +enum MemoizeDirectiveState { + Enabled = 'Enabled', + Disabled = 'Disabled', + Undefined = 'Undefined', } + +const ENABLED_OR_UNDEFINED_STATES = new Set([ + MemoizeDirectiveState.Enabled, + MemoizeDirectiveState.Undefined, +]); + +const ENABLED_OR_DISABLED_STATES = new Set([ + MemoizeDirectiveState.Enabled, + MemoizeDirectiveState.Disabled, +]); + function parseFunctions( source: string, language: 'flow' | 'typescript', ): Array<{ - compile: boolean; + compilationEnabled: boolean; fn: FunctionLike; }> { - const items: Array = []; + const items: Array<{ + compilationEnabled: boolean; + fn: FunctionLike; + }> = []; try { const ast = parseInput(source, language); traverse(ast, { FunctionDeclaration(nodePath) { - items.push(nodePath); + items.push({ + compilationEnabled: shouldCompile(nodePath), + fn: nodePath, + }); nodePath.skip(); }, ArrowFunctionExpression(nodePath) { - items.push(nodePath); + items.push({ + compilationEnabled: shouldCompile(nodePath), + fn: nodePath, + }); nodePath.skip(); }, FunctionExpression(nodePath) { - items.push(nodePath); + items.push({ + compilationEnabled: shouldCompile(nodePath), + fn: nodePath, + }); nodePath.skip(); }, }); @@ -115,45 +128,45 @@ function parseFunctions( }); } - return items.map<{ - compile: boolean; - fn: FunctionLike; - }>(fn => { - const {body} = fn.node; - if (t.isBlockStatement(body)) { - const selfCheck = checkExplicitMemoizeDirectives(body.directives); - if (selfCheck === MemoizeDirectiveState.Enabled) - return {compile: true, fn}; - if (selfCheck === MemoizeDirectiveState.Disabled) - return {compile: false, fn}; + return items; +} - const parentWithDirective = fn.findParent(parentPath => { - if (parentPath.isBlockStatement() || parentPath.isProgram()) { - const directiveCheck = checkExplicitMemoizeDirectives( - parentPath.node.directives, - ); - return [ - MemoizeDirectiveState.Enabled, - MemoizeDirectiveState.Disabled, - ].includes(directiveCheck); - } - return false; - }); +function shouldCompile(fn: FunctionLike): boolean { + const {body} = fn.node; + if (t.isBlockStatement(body)) { + const selfCheck = checkExplicitMemoizeDirectives(body.directives); + if (selfCheck === MemoizeDirectiveState.Enabled) return true; + if (selfCheck === MemoizeDirectiveState.Disabled) return false; - if (!parentWithDirective) return {compile: true, fn}; - const parentDirectiveCheck = checkExplicitMemoizeDirectives( - (parentWithDirective.node as t.Program | t.BlockStatement).directives, - ); - return { - compile: [ - MemoizeDirectiveState.Enabled, - MemoizeDirectiveState.Undefined, - ].includes(parentDirectiveCheck), - fn, - }; - } - return {compile: false, fn}; - }); + const parentWithDirective = fn.findParent(parentPath => { + if (parentPath.isBlockStatement() || parentPath.isProgram()) { + const directiveCheck = checkExplicitMemoizeDirectives( + parentPath.node.directives, + ); + return ENABLED_OR_DISABLED_STATES.has(directiveCheck); + } + return false; + }); + + if (!parentWithDirective) return true; + const parentDirectiveCheck = checkExplicitMemoizeDirectives( + (parentWithDirective.node as t.Program | t.BlockStatement).directives, + ); + return ENABLED_OR_UNDEFINED_STATES.has(parentDirectiveCheck); + } + return false; +} + +function checkExplicitMemoizeDirectives( + directives: Array, +): MemoizeDirectiveState { + if (findDirectiveEnablingMemoization(directives).length) { + return MemoizeDirectiveState.Enabled; + } + if (findDirectiveDisablingMemoization(directives).length) { + return MemoizeDirectiveState.Disabled; + } + return MemoizeDirectiveState.Undefined; } const COMMON_HOOKS: Array<[string, Hook]> = [ @@ -268,7 +281,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { for (const func of parsedFunctions) { const id = withIdentifier(getFunctionIdentifier(func.fn)); const fnName = id.name; - if (!func.compile) { + if (!func.compilationEnabled) { upsert({ kind: 'ast', fnName, From 42d46451cf1ef0a388579ff89e1dc9ec1c00167c Mon Sep 17 00:00:00 2001 From: Aditya Subramanyam Date: Sun, 17 Nov 2024 00:42:08 +0530 Subject: [PATCH 5/6] [playground] refactor variables and placement of constants to the top --- .../components/Editor/EditorImpl.tsx | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index 68185a6d4c677..ad8df1d760042 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -49,7 +49,21 @@ type FunctionLike = | NodePath | NodePath | NodePath; +enum MemoizeDirectiveState { + Enabled = 'Enabled', + Disabled = 'Disabled', + Undefined = 'Undefined', +} +const MEMOIZE_ENABLED_OR_UNDEFINED_STATES = new Set([ + MemoizeDirectiveState.Enabled, + MemoizeDirectiveState.Undefined, +]); + +const MEMOIZE_ENABLED_OR_DISABLED_STATES = new Set([ + MemoizeDirectiveState.Enabled, + MemoizeDirectiveState.Disabled, +]); function parseInput(input: string, language: 'flow' | 'typescript'): any { // Extract the first line to quickly check for custom test directives if (language === 'flow') { @@ -66,21 +80,6 @@ function parseInput(input: string, language: 'flow' | 'typescript'): any { }); } } -enum MemoizeDirectiveState { - Enabled = 'Enabled', - Disabled = 'Disabled', - Undefined = 'Undefined', -} - -const ENABLED_OR_UNDEFINED_STATES = new Set([ - MemoizeDirectiveState.Enabled, - MemoizeDirectiveState.Undefined, -]); - -const ENABLED_OR_DISABLED_STATES = new Set([ - MemoizeDirectiveState.Enabled, - MemoizeDirectiveState.Disabled, -]); function parseFunctions( source: string, @@ -143,7 +142,7 @@ function shouldCompile(fn: FunctionLike): boolean { const directiveCheck = checkExplicitMemoizeDirectives( parentPath.node.directives, ); - return ENABLED_OR_DISABLED_STATES.has(directiveCheck); + return MEMOIZE_ENABLED_OR_DISABLED_STATES.has(directiveCheck); } return false; }); @@ -152,7 +151,7 @@ function shouldCompile(fn: FunctionLike): boolean { const parentDirectiveCheck = checkExplicitMemoizeDirectives( (parentWithDirective.node as t.Program | t.BlockStatement).directives, ); - return ENABLED_OR_UNDEFINED_STATES.has(parentDirectiveCheck); + return MEMOIZE_ENABLED_OR_UNDEFINED_STATES.has(parentDirectiveCheck); } return false; } From e1322133019ece002392cfbfae7f33fb1430a4d3 Mon Sep 17 00:00:00 2001 From: Aditya Subramanyam Date: Sun, 17 Nov 2024 02:11:20 +0530 Subject: [PATCH 6/6] [playground] added more test cases and fixed minor bug where array functions and expression names werent getting printed --- .../{user-output.txt => 01-user-output.txt} | 0 ...fault-output.txt => 02-default-output.txt} | 0 ...ctive-arrow-function-expression-output.txt | 20 ++ ...-memo-arrow-function-expression-output.txt | 32 +++ ...e-use-memo-function-declaration-output.txt | 32 +++ ...se-no-memo-function-declaration-output.txt | 32 +++ ...use-no-memo-function-expression-output.txt | 18 ++ .../module-scope-use-memo-output.txt | 15 ++ ...use-no-memo-function-expression-output.txt | 3 + .../module-scope-use-no-memo-output.txt | 7 + .../page.spec.ts/simple-use-memo-output.txt | 19 -- .../playground/__tests__/e2e/page.spec.ts | 218 +++++++++++++++--- .../components/Editor/EditorImpl.tsx | 6 +- 13 files changed, 356 insertions(+), 46 deletions(-) rename compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/{user-output.txt => 01-user-output.txt} (100%) rename compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/{default-output.txt => 02-default-output.txt} (100%) create mode 100644 compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-no-directive-arrow-function-expression-output.txt create mode 100644 compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-use-memo-arrow-function-expression-output.txt create mode 100644 compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-use-memo-function-declaration-output.txt create mode 100644 compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-use-no-memo-function-declaration-output.txt create mode 100644 compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-use-no-memo-function-expression-output.txt create mode 100644 compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt create mode 100644 compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-function-expression-output.txt create mode 100644 compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt delete mode 100644 compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/simple-use-memo-output.txt diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/user-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt similarity index 100% rename from compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/user-output.txt rename to compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/01-user-output.txt diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/default-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt similarity index 100% rename from compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/default-output.txt rename to compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/02-default-output.txt diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-no-directive-arrow-function-expression-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-no-directive-arrow-function-expression-output.txt new file mode 100644 index 0000000000000..db441a034c50a --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-no-directive-arrow-function-expression-output.txt @@ -0,0 +1,20 @@ +function anonymous_1() { +  "use no memo"; +  const Avatar = () => { +    return 
Avatar Content
; +  }; +  const MemoizedAvatar = React.memo(Avatar); +  const Bio = () => { +    const handleBioUpdate = () => { +      console.log("Bio updated"); +    }; +    return Bio Content; +  }; +  const MemoizedBio = React.memo(Bio); +  return ( +    
+       +       +    
+  ); +} \ No newline at end of file diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-use-memo-arrow-function-expression-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-use-memo-arrow-function-expression-output.txt new file mode 100644 index 0000000000000..974ceeb8b7af8 --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-use-memo-arrow-function-expression-output.txt @@ -0,0 +1,32 @@ +function anonymous_1() { +  "use memo"; +  const $ = _c(3); +  const Chart = _temp2; +  let t0; +  if ($[0] === Symbol.for("react.memo_cache_sentinel")) { +    t0 = React.memo(Chart); +    $[0] = t0; +  } else { +    t0 = $[0]; +  } +  const MemoizedChart = t0; +  const Graph = _temp3; +  let t1; +  if ($[1] === Symbol.for("react.memo_cache_sentinel")) { +    t1 = React.memo(Graph); +    $[1] = t1; +  } else { +    t1 = $[1]; +  } +  const MemoizedGraph = t1; +  let t2; +  if ($[2] === Symbol.for("react.memo_cache_sentinel")) { +    t2 = ( +      
+         +         +      
+    ); +    $[2] = t2; +  } else { +    t2 = $[2]; \ No newline at end of file diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-use-memo-function-declaration-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-use-memo-function-declaration-output.txt new file mode 100644 index 0000000000000..3caacf369cebf --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-use-memo-function-declaration-output.txt @@ -0,0 +1,32 @@ +function App() { +  "use memo"; +  const $ = _c(3); +  let t0; +  if ($[0] === Symbol.for("react.memo_cache_sentinel")) { +    const Sidebar = function Sidebar() { +      const handleToggle = _temp; +      return Sidebar Content; +    }; +    t0 = React.memo(Sidebar); +    $[0] = t0; +  } else { +    t0 = $[0]; +  } +  const MemoizedSidebar = t0; +  let t1; +  if ($[1] === Symbol.for("react.memo_cache_sentinel")) { +    const Content = function Content() { +      return 
Main Content
; +    }; +    t1 = React.memo(Content); +    $[1] = t1; +  } else { +    t1 = $[1]; +  } +  const MemoizedContent = t1; +  let t2; +  if ($[2] === Symbol.for("react.memo_cache_sentinel")) { +    t2 = ( +      
+         \ No newline at end of file diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-use-no-memo-function-declaration-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-use-no-memo-function-declaration-output.txt new file mode 100644 index 0000000000000..51fb860de27eb --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-use-no-memo-function-declaration-output.txt @@ -0,0 +1,32 @@ +function Settings() { +  "use memo"; +  const $ = _c(3); +  let t0; +  if ($[0] === Symbol.for("react.memo_cache_sentinel")) { +    t0 = function Preferences() { +      const handleSave = _temp; +      return Preferences Content; +    }; +    $[0] = t0; +  } else { +    t0 = $[0]; +  } +  const Preferences = t0; +  let t1; +  if ($[1] === Symbol.for("react.memo_cache_sentinel")) { +    t1 = function Notifications() { +      return 
Notifications Settings
; +    }; +    $[1] = t1; +  } else { +    t1 = $[1]; +  } +  const Notifications = t1; +  let t2; +  if ($[2] === Symbol.for("react.memo_cache_sentinel")) { +    t2 = ( +      
+         +         +      
\ No newline at end of file diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-use-no-memo-function-expression-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-use-no-memo-function-expression-output.txt new file mode 100644 index 0000000000000..2c31ad8e50662 --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/function-scope-use-no-memo-function-expression-output.txt @@ -0,0 +1,18 @@ +function anonymous_1() { +  "use no memo"; +  const Widget = function () { +    const handleExpand = () => { +      console.log("Widget expanded"); +    }; +    return Widget Content
; +  }; +  const Panel = function () { +    return 
Panel Information
; +  }; +  return ( +    
+       +       +    
+  ); +} \ No newline at end of file diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt new file mode 100644 index 0000000000000..3b26c022db289 --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-memo-output.txt @@ -0,0 +1,15 @@ +function anonymous_1() { +  const $ = _c(1); +  const handleClick = _temp; +  let t0; +  if ($[0] === Symbol.for("react.memo_cache_sentinel")) { +    t0 = Welcome to the App!; +    $[0] = t0; +  } else { +    t0 = $[0]; +  } +  return t0; +} +function _temp() { +  console.log("Header clicked"); +} \ No newline at end of file diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-function-expression-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-function-expression-output.txt new file mode 100644 index 0000000000000..7af2442b5c8dc --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-function-expression-output.txt @@ -0,0 +1,3 @@ +function anonymous_1() { +  return ; +} \ No newline at end of file diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt new file mode 100644 index 0000000000000..3433dfeaae094 --- /dev/null +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/module-scope-use-no-memo-output.txt @@ -0,0 +1,7 @@ +function anonymous_1() { +  const handleMouseOver = () => { +    console.log("Footer hovered"); +  }; +  return Footer  +      Information; +} \ No newline at end of file diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/simple-use-memo-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/simple-use-memo-output.txt deleted file mode 100644 index b64206a7dc36b..0000000000000 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/simple-use-memo-output.txt +++ /dev/null @@ -1,19 +0,0 @@ -function useFoo(props) { -  "use no memo"; -  const x = () => {}; -  const y = function (a) {}; -  return foo(props.x, x); -} -function Component() { -  const $ = _c(2); -  const x = useFoo(); -  let t0; -  if ($[0] !== x) { -    t0 = 
{x}
; -    $[0] = x; -    $[1] = t0; -  } else { -    t0 = $[1]; -  } -  return t0; -} \ No newline at end of file diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts index f938cbe363b16..1c2b2317d512d 100644 --- a/compiler/apps/playground/__tests__/e2e/page.spec.ts +++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts @@ -8,10 +8,184 @@ import {expect, test} from '@playwright/test'; import {encodeStore, type Store} from '../../lib/stores'; +test.describe.configure({mode: 'parallel'}); + function concat(data: Array): string { return data.join(''); } +const DIRECTIVE_TEST_CASES = [ + { + name: 'module-scope-use-memo', + input: `'use memo'; + +const Header = () => { + const handleClick = () => { + console.log('Header clicked'); + }; + + return

Welcome to the App!

; +};`, + }, + { + name: 'module-scope-use-no-memo', + input: `'use no memo'; + +const Footer = () => { + const handleMouseOver = () => { + console.log('Footer hovered'); + }; + + return
Footer Information
; +}; +`, + }, + { + name: 'function-scope-use-memo-function-declaration', + input: `function App() { + 'use memo'; + + function Sidebar() { + const handleToggle = () => { + console.log('Sidebar toggled'); + }; + + return ; + } + + const MemoizedSidebar = React.memo(Sidebar); + + function Content() { + return
Main Content
; + } + + const MemoizedContent = React.memo(Content); + + return ( +
+ + +
+ ); +}`, + }, + { + name: 'function-scope-use-no-memo-function-expression', + input: `const Dashboard = function() { + 'use no memo'; + const Widget = function() { + const handleExpand = () => { + console.log('Widget expanded'); + }; + + return
Widget Content
; + }; + + const Panel = function() { + return
Panel Information
; + }; + + return ( +
+ + +
+ ); +};`, + }, + { + name: 'function-scope-use-memo-arrow-function-expression', + input: `const Analytics = () => { + 'use memo'; + + const Chart = () => { + const handleRefresh = () => { + console.log('Chart refreshed'); + }; + + return
Chart Content
; + }; + + const MemoizedChart = React.memo(Chart); + + const Graph = () => { + return
Graph Content
; + }; + + const MemoizedGraph = React.memo(Graph); + return ( +
+ + +
+ ); +};`, + }, + { + name: 'module-scope-use-no-memo-function-expression', + input: `'use no memo'; + +const Sidebar = function() { + return ; +};`, + }, + { + name: 'function-scope-no-directive-arrow-function-expression', + input: ` +const Profile = () => { +'use no memo'; + const Avatar = () => { + return
Avatar Content
; + }; + + const MemoizedAvatar = React.memo(Avatar); + + const Bio = () => { + const handleBioUpdate = () => { + console.log('Bio updated'); + }; + + return
Bio Content
; + }; + + const MemoizedBio = React.memo(Bio); + + return ( +
+ + +
+ ); +};`, + }, + { + name: 'function-scope-use-no-memo-function-declaration', + input: `'use no memo'; + +function Settings() { + 'use memo'; + + function Preferences() { + const handleSave = () => { + console.log('Preferences saved'); + }; + + return
Preferences Content
; + } + + function Notifications() { + return
Notifications Settings
; + } + + return ( +
+ + +
+ ); +}`, + }, +]; test('editor should open successfully', async ({page}) => { await page.goto(`/`, {waitUntil: 'networkidle'}); await page.screenshot({ @@ -36,7 +210,7 @@ test('editor should compile from hash successfully', async ({page}) => { }); const userInput = (await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? []; - expect(concat(userInput)).toMatchSnapshot('user-output.txt'); + expect(concat(userInput)).toMatchSnapshot('01-user-output.txt'); }); test('reset button works', async ({page}) => { const store: Store = { @@ -57,30 +231,22 @@ test('reset button works', async ({page}) => { }); const defaultInput = (await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? []; - expect(concat(defaultInput)).toMatchSnapshot('default-output.txt'); + expect(concat(defaultInput)).toMatchSnapshot('02-default-output.txt'); }); -test('directives work', async ({page}) => { - const store: Store = { - source: `function useFoo(props) { - 'use no memo'; - const x = () => { }; - const y = function (a) { }; - return foo(props.x, x); - } - function Component() { - const x = useFoo(); - return
{x}
; - } - `, - }; - const hash = encodeStore(store); - await page.goto(`/#${hash}`, {waitUntil: 'networkidle'}); - await page.screenshot({ - fullPage: true, - path: 'test-results/03-simple-use-memo.png', - }); +DIRECTIVE_TEST_CASES.forEach((t, idx) => + test(`directives work: ${t.name}`, async ({page}) => { + const store: Store = { + source: t.input, + }; + const hash = encodeStore(store); + await page.goto(`/#${hash}`, {waitUntil: 'networkidle'}); + await page.screenshot({ + fullPage: true, + path: `test-results/03-0${idx}-${t.name}.png`, + }); - const useMemoOutput = - (await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? []; - expect(concat(useMemoOutput)).toMatchSnapshot('simple-use-memo-output.txt'); -}); + const useMemoOutput = + (await page.locator('.monaco-editor').nth(1).allInnerTexts()) ?? []; + expect(concat(useMemoOutput)).toMatchSnapshot(`${t.name}-output.txt`); + }), +); diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index ad8df1d760042..82a40272bd312 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -287,7 +287,11 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { name: 'CodeGen', value: { type: 'FunctionDeclaration', - id: func.fn.isArrowFunctionExpression() ? null : func.fn.node.id, + id: + func.fn.isArrowFunctionExpression() || + func.fn.isFunctionExpression() + ? withIdentifier(null) + : func.fn.node.id, async: func.fn.node.async, generator: !!func.fn.node.generator, body: func.fn.node.body as t.BlockStatement,