fix: CodeNodeEditor walk cannot read properties of null (#11129)
Some checks are pending
Test Master / install-and-build (push) Waiting to run
Test Master / Unit tests (18.x) (push) Blocked by required conditions
Test Master / Unit tests (20.x) (push) Blocked by required conditions
Test Master / Unit tests (22.4) (push) Blocked by required conditions
Test Master / Lint (push) Blocked by required conditions
Test Master / Notify Slack on failure (push) Blocked by required conditions

Co-authored-by: Elias Meire <elias@meire.dev>
This commit is contained in:
Michael Kret 2024-11-21 11:16:10 +02:00 committed by GitHub
parent 40dd02f360
commit d99e0a7c97
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 61 additions and 13 deletions

View file

@ -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'),
]),
]);
});
});
});

View file

@ -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 { Completion } from '@codemirror/autocomplete';
import type { Node } from 'estree';
import type { RangeNode } from './types'; import type { RangeNode } from './types';
import { sanitizeHtml } from '@/utils/htmlUtils'; import { sanitizeHtml } from '@/utils/htmlUtils';
import type { Node } from 'estree';
export function walk<T extends RangeNode>( export function walk<T extends RangeNode>(
node: Node | esprima.Program, node: Node | esprima.Program,
test: (node: Node) => boolean, test: (node: Node) => boolean,
found: Node[] = [], found: Node[] = [],
) { ) {
// @ts-ignore const isProgram = node.type === esprima.Syntax.Program;
if (test(node)) found.push(node); if (!isProgram && test(node)) found.push(node);
for (const key in node) { if (isProgram) {
if (!(key in node)) continue; node.body.forEach((n) => walk(n as Node, test, found));
} else {
for (const key in node) {
if (!(key in node)) continue;
// @ts-ignore // @ts-expect-error Node is not string indexable, but it has many possible properties
const child = node[key]; const child = node[key];
if (child === null || typeof child !== 'object') continue; if (child === null || typeof child !== 'object') continue;
if (Array.isArray(child)) { if (Array.isArray(child)) {
child.forEach((node) => walk(node, test, found)); child.filter(Boolean).forEach((n) => walk(n, test, found));
} else { } else {
walk(child, test, found); walk(child, test, found);
}
} }
} }