Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit d72a89e

Browse files
committed
refactor(nuxt): replace runInNewContext with AST walker
1 parent 2cce6fb commit d72a89e

2 files changed

Lines changed: 149 additions & 30 deletions

File tree

packages/nuxt/src/pages/utils.ts

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { runInNewContext } from 'node:vm'
21
import fs from 'node:fs'
32
import { normalize, relative } from 'pathe'
43
import { joinURL } from 'ufo'
@@ -519,51 +518,61 @@ export function resolveRoutePaths (page: NuxtPage, parent = '/'): string[] {
519518
}
520519

521520
export function isSerializable (code: string, node: Node): { value?: any, serializable: boolean } {
522-
if (node.type === 'ObjectExpression') {
523-
const valueString = code.slice(node.start, node.end)
524-
try {
525-
return {
526-
value: JSON.parse(runInNewContext(`JSON.stringify(${valueString})`, {})),
527-
serializable: true,
528-
}
529-
} catch {
530-
return {
531-
serializable: false,
532-
}
521+
if (node.type === 'Literal') {
522+
if (typeof node.value === 'string' || typeof node.value === 'number' || typeof node.value === 'boolean' || node.value === null) {
523+
return { value: node.value, serializable: true }
533524
}
525+
return { serializable: false }
526+
}
527+
528+
if (node.type === 'UnaryExpression' && (node.operator === '-' || node.operator === '+')) {
529+
const arg = node.argument
530+
if (arg.type === 'Literal' && typeof arg.value === 'number') {
531+
return { value: node.operator === '-' ? -arg.value : arg.value, serializable: true }
532+
}
533+
return { serializable: false }
534534
}
535535

536536
if (node.type === 'ArrayExpression') {
537-
const values: string[] = []
537+
const values: any[] = []
538538
for (const element of node.elements) {
539-
if (!element) {
540-
continue
539+
// `null` element is a sparse-array hole.
540+
if (!element || element.type === 'SpreadElement') {
541+
return { serializable: false }
541542
}
542543
const { serializable, value } = isSerializable(code, element)
543544
if (!serializable) {
544-
return {
545-
serializable: false,
546-
}
545+
return { serializable: false }
547546
}
548547
values.push(value)
549548
}
550-
551-
return {
552-
value: values,
553-
serializable: true,
554-
}
549+
return { value: values, serializable: true }
555550
}
556551

557-
if (node.type === 'Literal' && (typeof node.value === 'string' || typeof node.value === 'boolean' || typeof node.value === 'number' || node.value === null)) {
558-
return {
559-
value: node.value,
560-
serializable: true,
552+
if (node.type === 'ObjectExpression') {
553+
const value: Record<string, any> = {}
554+
for (const property of node.properties) {
555+
if (property.type !== 'Property' || property.computed || property.kind !== 'init' || property.method) {
556+
return { serializable: false }
557+
}
558+
let key: string
559+
if (property.key.type === 'Identifier') {
560+
key = property.key.name
561+
} else if (property.key.type === 'Literal' && (typeof property.key.value === 'string' || typeof property.key.value === 'number')) {
562+
key = String(property.key.value)
563+
} else {
564+
return { serializable: false }
565+
}
566+
const { serializable, value: propertyValue } = isSerializable(code, property.value)
567+
if (!serializable) {
568+
return { serializable: false }
569+
}
570+
value[key] = propertyValue
561571
}
572+
return { value, serializable: true }
562573
}
563574

564-
return {
565-
serializable: false,
566-
}
575+
return { serializable: false }
567576
}
568577

569578
export function toRou3Patterns (pages: NuxtPage[], prefix = '/'): string[] {
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { parseSync } from 'oxc-parser'
3+
import type { Expression, VariableDeclaration } from 'oxc-parser'
4+
5+
import { isSerializable } from '../src/pages/utils.ts'
6+
7+
function parseExpression (source: string): { code: string, node: Expression } {
8+
// Parse as a variable initialiser so a leading `{` isn't ambiguous with a block, and we get the
9+
// raw expression node rather than a ParenthesizedExpression wrapper.
10+
const code = `const __x = ${source}`
11+
const ast = parseSync('test.js', code, { lang: 'js' })
12+
const decl = ast.program.body[0] as VariableDeclaration
13+
return { code, node: decl.declarations[0]!.init as Expression }
14+
}
15+
16+
function check (source: string) {
17+
const { code, node } = parseExpression(source)
18+
return isSerializable(code, node)
19+
}
20+
21+
describe('isSerializable', () => {
22+
it('accepts a plain literal object', () => {
23+
expect(check(`{ foo: 'bar', baz: 1, qux: true, quux: null }`)).toEqual({
24+
value: { foo: 'bar', baz: 1, qux: true, quux: null },
25+
serializable: true,
26+
})
27+
})
28+
29+
it('accepts a nested object containing an array', () => {
30+
expect(check(`{ a: { b: [1, 2, 3] } }`)).toEqual({
31+
value: { a: { b: [1, 2, 3] } },
32+
serializable: true,
33+
})
34+
})
35+
36+
it('accepts signed numeric literals', () => {
37+
expect(check(`{ x: -1, y: +2 }`)).toEqual({
38+
value: { x: -1, y: 2 },
39+
serializable: true,
40+
})
41+
})
42+
43+
it('accepts string-keyed properties', () => {
44+
expect(check(`{ 'foo-bar': 1, '123': 'baz' }`)).toEqual({
45+
value: { 'foo-bar': 1, '123': 'baz' },
46+
serializable: true,
47+
})
48+
})
49+
50+
it('rejects identifier references', () => {
51+
expect(check(`{ foo: bar }`)).toEqual({ serializable: false })
52+
})
53+
54+
it('rejects call expressions', () => {
55+
expect(check(`{ foo: bar() }`)).toEqual({ serializable: false })
56+
})
57+
58+
it('rejects spread elements in objects', () => {
59+
expect(check(`{ ...rest }`)).toEqual({ serializable: false })
60+
})
61+
62+
it('rejects spread elements in arrays', () => {
63+
expect(check(`[1, ...rest, 3]`)).toEqual({ serializable: false })
64+
})
65+
66+
it('rejects computed keys', () => {
67+
expect(check(`{ [k]: 1 }`)).toEqual({ serializable: false })
68+
})
69+
70+
it('rejects template literals', () => {
71+
expect(check('{ foo: `hello` }')).toEqual({ serializable: false })
72+
})
73+
74+
it('rejects regex literals', () => {
75+
expect(check(`{ foo: /x/ }`)).toEqual({ serializable: false })
76+
})
77+
78+
it('rejects bigint literals', () => {
79+
expect(check(`{ foo: 1n }`)).toEqual({ serializable: false })
80+
})
81+
82+
it('rejects method shorthand', () => {
83+
expect(check(`{ foo () {} }`)).toEqual({ serializable: false })
84+
})
85+
86+
it('rejects getters', () => {
87+
expect(check(`{ get foo () { return 1 } }`)).toEqual({ serializable: false })
88+
})
89+
90+
it('rejects setters', () => {
91+
expect(check(`{ set foo (v) {} }`)).toEqual({ serializable: false })
92+
})
93+
94+
it('rejects sparse arrays', () => {
95+
expect(check(`[1, , 3]`)).toEqual({ serializable: false })
96+
})
97+
98+
it('rejects unary operators other than + and -', () => {
99+
expect(check(`{ x: !true }`)).toEqual({ serializable: false })
100+
expect(check(`{ x: ~1 }`)).toEqual({ serializable: false })
101+
})
102+
103+
it('rejects signed non-numeric literals', () => {
104+
expect(check(`{ x: -'foo' }`)).toEqual({ serializable: false })
105+
})
106+
107+
it('rejects arrow function values', () => {
108+
expect(check(`{ foo: () => 1 }`)).toEqual({ serializable: false })
109+
})
110+
})

0 commit comments

Comments
 (0)