From d99e0a7c979a1ee96b2eea1b9011d5bce375289a Mon Sep 17 00:00:00 2001 From: Michael Kret <88898367+michael-radency@users.noreply.github.com> Date: Thu, 21 Nov 2024 11:16:10 +0200 Subject: [PATCH] fix: CodeNodeEditor walk cannot read properties of null (#11129) Co-authored-by: Elias Meire --- .../components/CodeNodeEditor/utils.test.ts | 44 +++++++++++++++++++ .../src/components/CodeNodeEditor/utils.ts | 30 +++++++------ 2 files changed, 61 insertions(+), 13 deletions(-) create mode 100644 packages/editor-ui/src/components/CodeNodeEditor/utils.test.ts diff --git a/packages/editor-ui/src/components/CodeNodeEditor/utils.test.ts b/packages/editor-ui/src/components/CodeNodeEditor/utils.test.ts new file mode 100644 index 0000000000..a821403e4c --- /dev/null +++ b/packages/editor-ui/src/components/CodeNodeEditor/utils.test.ts @@ -0,0 +1,44 @@ +import * as esprima from 'esprima-next'; +import { walk } from './utils'; + +describe('CodeNodeEditor utils', () => { + describe('walk', () => { + it('should find the correct syntax nodes', () => { + const code = `const x = 'a' +function f(arg) { + arg['b'] = 1 + return arg +} + +const y = f({ a: 'c' }) +`; + const program = esprima.parse(code); + const stringLiterals = walk( + program, + (node) => node.type === esprima.Syntax.Literal && typeof node.value === 'string', + ); + expect(stringLiterals).toEqual([ + new esprima.Literal('a', "'a'"), + new esprima.Literal('b', "'b'"), + new esprima.Literal('c', "'c'"), + ]); + }); + + it('should handle null syntax nodes', () => { + // ,, in [1,,2] results in a `null` ArrayExpressionElement + const code = 'const fn = () => [1,,2]'; + const program = esprima.parse(code); + const arrayExpressions = walk( + program, + (node) => node.type === esprima.Syntax.ArrayExpression, + ); + expect(arrayExpressions).toEqual([ + new esprima.ArrayExpression([ + new esprima.Literal(1, '1'), + null, + new esprima.Literal(2, '2'), + ]), + ]); + }); + }); +}); diff --git a/packages/editor-ui/src/components/CodeNodeEditor/utils.ts b/packages/editor-ui/src/components/CodeNodeEditor/utils.ts index d37bc87648..e75dfa927a 100644 --- a/packages/editor-ui/src/components/CodeNodeEditor/utils.ts +++ b/packages/editor-ui/src/components/CodeNodeEditor/utils.ts @@ -1,29 +1,33 @@ -import type * as esprima from 'esprima-next'; +import * as esprima from 'esprima-next'; import type { Completion } from '@codemirror/autocomplete'; -import type { Node } from 'estree'; import type { RangeNode } from './types'; import { sanitizeHtml } from '@/utils/htmlUtils'; +import type { Node } from 'estree'; export function walk( node: Node | esprima.Program, test: (node: Node) => boolean, found: Node[] = [], ) { - // @ts-ignore - if (test(node)) found.push(node); + const isProgram = node.type === esprima.Syntax.Program; + if (!isProgram && test(node)) found.push(node); - for (const key in node) { - if (!(key in node)) continue; + if (isProgram) { + node.body.forEach((n) => walk(n as Node, test, found)); + } else { + for (const key in node) { + if (!(key in node)) continue; - // @ts-ignore - const child = node[key]; + // @ts-expect-error Node is not string indexable, but it has many possible properties + const child = node[key]; - if (child === null || typeof child !== 'object') continue; + if (child === null || typeof child !== 'object') continue; - if (Array.isArray(child)) { - child.forEach((node) => walk(node, test, found)); - } else { - walk(child, test, found); + if (Array.isArray(child)) { + child.filter(Boolean).forEach((n) => walk(n, test, found)); + } else { + walk(child, test, found); + } } }