Skip to content

Commit

Permalink
refactor(define-options): disallow more keys
Browse files Browse the repository at this point in the history
  • Loading branch information
sxzz committed May 23, 2023
1 parent bd7fb99 commit 5806748
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 117 deletions.
5 changes: 5 additions & 0 deletions .changeset/thirty-kids-rule.md
@@ -0,0 +1,5 @@
---
'unplugin-vue-define-options': patch
---

disallow more keys for `defineOptions`
107 changes: 106 additions & 1 deletion packages/define-options/src/core/index.ts
@@ -1,2 +1,107 @@
export * from './transform'
import {
DEFINE_OPTIONS,
MagicString,
addNormalScript,
checkInvalidScopeReference,
getTransformResult,
importHelperFn,
parseSFC,
} from '@vue-macros/common'
import { walkAST } from 'ast-walker-scope'
import {
type ExportDefaultDeclaration,
type Program,
type Statement,
} from '@babel/types'
import { filterMacro, hasPropsOrEmits as hasDisallowedProp } from './utils'

export * from './utils'

export function transformDefineOptions(code: string, id: string) {
if (!code.includes(DEFINE_OPTIONS)) return

const sfc = parseSFC(code, id)
if (!sfc.scriptSetup) return
const { scriptSetup, getSetupAst, getScriptAst } = sfc
const setupOffset = scriptSetup.loc.start.offset
const setupAst = getSetupAst()!

const nodes = filterMacro(setupAst!.body)
if (nodes.length === 0) {
return
} else if (nodes.length > 1)
throw new SyntaxError(`duplicate ${DEFINE_OPTIONS}() call`)

const scriptAst = getScriptAst()!
if (scriptAst) checkDefaultExport(scriptAst.body)

const setupBindings = getRootScopeIds(setupAst)

const s = new MagicString(code)

const [node] = nodes
const [arg] = node.arguments
if (arg) {
const normalScript = addNormalScript(sfc, s)

const scriptOffset = normalScript.start()

s.appendLeft(
scriptOffset,
`\nexport default /*#__PURE__*/ ${importHelperFn(
s,
scriptOffset,
'defineComponent'
)}(`
)

if (arg.type === 'ObjectExpression' && hasDisallowedProp(arg))
throw new SyntaxError(
`${DEFINE_OPTIONS}() please use defineProps, defineEmits, defineExpose, or defineSlots instead.`
)

checkInvalidScopeReference(arg, DEFINE_OPTIONS, setupBindings)

s.moveNode(arg, scriptOffset, { offset: setupOffset })

// removes defineOptions()
s.remove(setupOffset + node.start!, setupOffset + arg.start!)
s.remove(setupOffset + arg.end!, setupOffset + node.end!)

s.appendRight(scriptOffset, ');')
normalScript.end()
} else {
// removes defineOptions()
s.removeNode(node, { offset: setupOffset })
}

return getTransformResult(s, id)
}

function checkDefaultExport(stmts: Statement[]) {
const hasDefaultExport = stmts.some(
(node): node is ExportDefaultDeclaration =>
node.type === 'ExportDefaultDeclaration'
)
if (hasDefaultExport)
throw new SyntaxError(
`${DEFINE_OPTIONS} cannot be used with default export within <script>.`
)
}

function getRootScopeIds(program: Program) {
let ids: string[] = []
walkAST(program, {
enter(node) {
if (node.type === 'BlockStatement') {
this.skip()
}
},
leave(node) {
if (node.type !== 'Program') return
ids = Object.keys(this.scope)
},
})

return ids
}
112 changes: 0 additions & 112 deletions packages/define-options/src/core/transform.ts

This file was deleted.

5 changes: 4 additions & 1 deletion packages/define-options/src/core/utils.ts
Expand Up @@ -21,6 +21,9 @@ export function hasPropsOrEmits(node: ObjectExpression) {
(prop) =>
(prop.type === 'ObjectProperty' || prop.type === 'ObjectMethod') &&
prop.key.type === 'Identifier' &&
(prop.key.name === 'props' || prop.key.name === 'emits')
(prop.key.name === 'props' ||
prop.key.name === 'emits' ||
prop.key.name === 'expose' ||
prop.key.name === 'slots')
)
}
2 changes: 1 addition & 1 deletion packages/define-options/src/index.ts
Expand Up @@ -8,7 +8,7 @@ import {
createFilter,
detectVueVersion,
} from '@vue-macros/common'
import { transformDefineOptions } from './core/transform'
import { transformDefineOptions } from './core'

export type Options = BaseOptions
export type OptionsResolved = MarkRequired<Options, 'include' | 'version'>
Expand Down
Expand Up @@ -186,9 +186,9 @@ exports[`fixtures > tests/fixtures/error2.vue 1`] = `"\`defineOptions()\` in <sc
exports[`fixtures > tests/fixtures/error3.vue 1`] = `"defineOptions cannot be used with default export within <script>."`;
exports[`fixtures > tests/fixtures/error4.vue 1`] = `"defineOptions() please use defineProps or defineEmits instead."`;
exports[`fixtures > tests/fixtures/error4.vue 1`] = `"defineOptions() please use defineProps, defineEmits, defineExpose, or defineSlots instead."`;
exports[`fixtures > tests/fixtures/error5.vue 1`] = `"defineOptions() please use defineProps or defineEmits instead."`;
exports[`fixtures > tests/fixtures/error5.vue 1`] = `"defineOptions() please use defineProps, defineEmits, defineExpose, or defineSlots instead."`;
exports[`fixtures > tests/fixtures/error6.vue 1`] = `"duplicate defineOptions() call"`;
Expand Down

0 comments on commit 5806748

Please sign in to comment.