mirror of
https://github.com/nuxt/nuxt.git
synced 2025-01-18 17:35:57 +00:00
fix(nuxt): ignore non-reference identifiers when extracting page metadata (#30381)
This commit is contained in:
parent
ce0c89c086
commit
ba764b212f
@ -476,7 +476,7 @@ function getPatternIdentifiers (pattern: WithLocations<Node>) {
|
|||||||
return identifiers
|
return identifiers
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNotReferencePosition (node: WithLocations<Node>, parent: WithLocations<Node> | null) {
|
export function isNotReferencePosition (node: WithLocations<Node>, parent: WithLocations<Node> | null) {
|
||||||
if (!parent || node.type !== 'Identifier') { return false }
|
if (!parent || node.type !== 'Identifier') { return false }
|
||||||
|
|
||||||
switch (parent.type) {
|
switch (parent.type) {
|
||||||
|
@ -3,7 +3,6 @@ import { createUnplugin } from 'unplugin'
|
|||||||
import { parseQuery, parseURL } from 'ufo'
|
import { parseQuery, parseURL } from 'ufo'
|
||||||
import type { StaticImport } from 'mlly'
|
import type { StaticImport } from 'mlly'
|
||||||
import { findExports, findStaticImports, parseStaticImport } from 'mlly'
|
import { findExports, findStaticImports, parseStaticImport } from 'mlly'
|
||||||
import { walk } from 'estree-walker'
|
|
||||||
import MagicString from 'magic-string'
|
import MagicString from 'magic-string'
|
||||||
import { isAbsolute } from 'pathe'
|
import { isAbsolute } from 'pathe'
|
||||||
import { logger } from '@nuxt/kit'
|
import { logger } from '@nuxt/kit'
|
||||||
@ -12,7 +11,9 @@ import {
|
|||||||
ScopeTracker,
|
ScopeTracker,
|
||||||
type ScopeTrackerNode,
|
type ScopeTrackerNode,
|
||||||
getUndeclaredIdentifiersInFunction,
|
getUndeclaredIdentifiersInFunction,
|
||||||
|
isNotReferencePosition,
|
||||||
parseAndWalk,
|
parseAndWalk,
|
||||||
|
walk,
|
||||||
withLocations,
|
withLocations,
|
||||||
} from '../../core/utils/parse'
|
} from '../../core/utils/parse'
|
||||||
|
|
||||||
@ -147,12 +148,26 @@ export const PageMetaPlugin = (options: PageMetaPluginOptions = {}) => createUnp
|
|||||||
declarationNodes.push(node)
|
declarationNodes.push(node)
|
||||||
}
|
}
|
||||||
|
|
||||||
function addImportOrDeclaration (name: string) {
|
/**
|
||||||
|
* Adds an import or a declaration to the extracted code.
|
||||||
|
* @param name The name of the import or declaration to add.
|
||||||
|
* @param node The node that is currently being processed. (To detect self-references)
|
||||||
|
*/
|
||||||
|
function addImportOrDeclaration (name: string, node?: ScopeTrackerNode) {
|
||||||
if (isStaticIdentifier(name)) {
|
if (isStaticIdentifier(name)) {
|
||||||
addImport(name)
|
addImport(name)
|
||||||
} else {
|
} else {
|
||||||
const declaration = scopeTracker.getDeclaration(name)
|
const declaration = scopeTracker.getDeclaration(name)
|
||||||
if (declaration) {
|
/*
|
||||||
|
Without checking for `declaration !== node`, we would end up in an infinite loop
|
||||||
|
when, for example, a variable is declared and then used in its own initializer.
|
||||||
|
(we shouldn't mask the underlying error by throwing a `Maximum call stack size exceeded` error)
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const a = { b: a }
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
if (declaration && declaration !== node) {
|
||||||
processDeclaration(declaration)
|
processDeclaration(declaration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -160,32 +175,35 @@ export const PageMetaPlugin = (options: PageMetaPluginOptions = {}) => createUnp
|
|||||||
|
|
||||||
const scopeTracker = new ScopeTracker()
|
const scopeTracker = new ScopeTracker()
|
||||||
|
|
||||||
function processDeclaration (node: ScopeTrackerNode | null) {
|
function processDeclaration (scopeTrackerNode: ScopeTrackerNode | null) {
|
||||||
if (node?.type === 'Variable') {
|
if (scopeTrackerNode?.type === 'Variable') {
|
||||||
addDeclaration(node)
|
addDeclaration(scopeTrackerNode)
|
||||||
|
|
||||||
for (const decl of node.variableNode.declarations) {
|
for (const decl of scopeTrackerNode.variableNode.declarations) {
|
||||||
if (!decl.init) { continue }
|
if (!decl.init) { continue }
|
||||||
walk(decl.init, {
|
walk(decl.init, {
|
||||||
enter: (node) => {
|
enter: (node, parent) => {
|
||||||
if (node.type === 'AwaitExpression') {
|
if (node.type === 'AwaitExpression') {
|
||||||
logger.error(`[nuxt] Await expressions are not supported in definePageMeta. File: '${id}'`)
|
logger.error(`[nuxt] Await expressions are not supported in definePageMeta. File: '${id}'`)
|
||||||
throw new Error('await in definePageMeta')
|
throw new Error('await in definePageMeta')
|
||||||
}
|
}
|
||||||
if (node.type !== 'Identifier') { return }
|
if (
|
||||||
|
isNotReferencePosition(node, parent)
|
||||||
|
|| node.type !== 'Identifier' // checking for `node.type` to narrow down the type
|
||||||
|
) { return }
|
||||||
|
|
||||||
addImportOrDeclaration(node.name)
|
addImportOrDeclaration(node.name, scopeTrackerNode)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} else if (node?.type === 'Function') {
|
} else if (scopeTrackerNode?.type === 'Function') {
|
||||||
// arrow functions are going to be assigned to a variable
|
// arrow functions are going to be assigned to a variable
|
||||||
if (node.node.type === 'ArrowFunctionExpression') { return }
|
if (scopeTrackerNode.node.type === 'ArrowFunctionExpression') { return }
|
||||||
const name = node.node.id?.name
|
const name = scopeTrackerNode.node.id?.name
|
||||||
if (!name) { return }
|
if (!name) { return }
|
||||||
addDeclaration(node)
|
addDeclaration(scopeTrackerNode)
|
||||||
|
|
||||||
const undeclaredIdentifiers = getUndeclaredIdentifiersInFunction(node.node)
|
const undeclaredIdentifiers = getUndeclaredIdentifiersInFunction(scopeTrackerNode.node)
|
||||||
for (const name of undeclaredIdentifiers) {
|
for (const name of undeclaredIdentifiers) {
|
||||||
addImportOrDeclaration(name)
|
addImportOrDeclaration(name)
|
||||||
}
|
}
|
||||||
@ -203,8 +221,11 @@ export const PageMetaPlugin = (options: PageMetaPluginOptions = {}) => createUnp
|
|||||||
if (!meta) { return }
|
if (!meta) { return }
|
||||||
|
|
||||||
walk(meta, {
|
walk(meta, {
|
||||||
enter (node) {
|
enter (node, parent) {
|
||||||
if (node.type !== 'Identifier') { return }
|
if (
|
||||||
|
isNotReferencePosition(node, parent)
|
||||||
|
|| node.type !== 'Identifier' // checking for `node.type` to narrow down the type
|
||||||
|
) { return }
|
||||||
|
|
||||||
if (isStaticIdentifier(node.name)) {
|
if (isStaticIdentifier(node.name)) {
|
||||||
addImport(node.name)
|
addImport(node.name)
|
||||||
|
@ -467,4 +467,81 @@ definePageMeta({
|
|||||||
|
|
||||||
expect(wasErrorThrown).toBe(true)
|
expect(wasErrorThrown).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should only add definitions for reference identifiers', () => {
|
||||||
|
const sfc = `
|
||||||
|
<script setup lang="ts">
|
||||||
|
const foo = 'foo'
|
||||||
|
const bar = { bar: 'bar' }.bar, baz = { baz: 'baz' }.baz, x = { foo }
|
||||||
|
const test = 'test'
|
||||||
|
const prop = 'prop'
|
||||||
|
const num = 1
|
||||||
|
|
||||||
|
const val = 'val'
|
||||||
|
|
||||||
|
const useVal = () => ({ val: 'val' })
|
||||||
|
|
||||||
|
function recursive () {
|
||||||
|
recursive()
|
||||||
|
}
|
||||||
|
|
||||||
|
definePageMeta({
|
||||||
|
middleware: [
|
||||||
|
() => {
|
||||||
|
console.log(bar, baz)
|
||||||
|
recursive()
|
||||||
|
|
||||||
|
const val = useVal().val
|
||||||
|
const obj = {
|
||||||
|
num,
|
||||||
|
prop: 'prop',
|
||||||
|
}
|
||||||
|
|
||||||
|
const c = class test {
|
||||||
|
prop = 'prop'
|
||||||
|
test () {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
`
|
||||||
|
const res = compileScript(parse(sfc).descriptor, { id: 'component.vue' })
|
||||||
|
expect(transformPlugin.transform.call({
|
||||||
|
parse: (code: string, opts: any = {}) => Parser.parse(code, {
|
||||||
|
sourceType: 'module',
|
||||||
|
ecmaVersion: 'latest',
|
||||||
|
locations: true,
|
||||||
|
...opts,
|
||||||
|
}),
|
||||||
|
}, res.content, 'component.vue?macro=true')?.code).toMatchInlineSnapshot(`
|
||||||
|
"const foo = 'foo'
|
||||||
|
const num = 1
|
||||||
|
const bar = { bar: 'bar' }.bar, baz = { baz: 'baz' }.baz, x = { foo }
|
||||||
|
const useVal = () => ({ val: 'val' })
|
||||||
|
function recursive () {
|
||||||
|
recursive()
|
||||||
|
}
|
||||||
|
const __nuxt_page_meta = {
|
||||||
|
middleware: [
|
||||||
|
() => {
|
||||||
|
console.log(bar, baz)
|
||||||
|
recursive()
|
||||||
|
|
||||||
|
const val = useVal().val
|
||||||
|
const obj = {
|
||||||
|
num,
|
||||||
|
prop: 'prop',
|
||||||
|
}
|
||||||
|
|
||||||
|
const c = class test {
|
||||||
|
prop = 'prop'
|
||||||
|
test () {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
export default __nuxt_page_meta"
|
||||||
|
`)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user