Skip to content

Commit 4f7d0fd

Browse files
committed
feat(compiler-sfc): useAttrs support generic type
1 parent 91c1426 commit 4f7d0fd

File tree

3 files changed

+93
-1
lines changed

3 files changed

+93
-1
lines changed

packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap

+33
Original file line numberDiff line numberDiff line change
@@ -1286,6 +1286,39 @@ return { get aa() { return aa }, set aa(v) { aa = v }, bb, cc, dd, get a() { ret
12861286
}"
12871287
`;
12881288

1289+
exports[`SFC compile <script setup> useAttrs w/ generic type 1`] = `
1290+
"import { defineComponent as _defineComponent } from 'vue'
1291+
import { useAttrs, ref} from 'vue'
1292+
1293+
export default /*#__PURE__*/_defineComponent({
1294+
setup(__props, { expose }) {
1295+
expose();
1296+
1297+
const attrs = useAttrs<{ foo: number }>()
1298+
1299+
return { attrs, useAttrs, ref }
1300+
}
1301+
1302+
}, { attrs: {} as { foo: number }})"
1303+
`;
1304+
1305+
exports[`SFC compile <script setup> useAttrs w/ type reference 1`] = `
1306+
"import { defineComponent as _defineComponent } from 'vue'
1307+
import { useAttrs, ref} from 'vue'
1308+
type CompAttrs = { foo: number }
1309+
1310+
export default /*#__PURE__*/_defineComponent({
1311+
setup(__props, { expose }) {
1312+
expose();
1313+
1314+
const attrs = useAttrs<CompAttrs>()
1315+
1316+
return { attrs, useAttrs, ref }
1317+
}
1318+
1319+
}, { attrs: {} as { foo: number }})"
1320+
`;
1321+
12891322
exports[`SFC compile <script setup> with TypeScript const Enum 1`] = `
12901323
"import { defineComponent as _defineComponent } from 'vue'
12911324
const enum Foo { A = 123 }

packages/compiler-sfc/__tests__/compileScript.spec.ts

+23
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,29 @@ const bar = 1
112112
assertCode(content)
113113
})
114114

115+
test('useAttrs w/ generic type', () => {
116+
const { content } = compile(`
117+
<script lang="ts" setup>
118+
import { useAttrs, ref} from 'vue'
119+
const attrs = useAttrs<{ foo: number }>()
120+
</script>
121+
`)
122+
assertCode(content)
123+
expect(content).toMatch('{ attrs: {} as { foo: number }}')
124+
})
125+
126+
test('useAttrs w/ type reference', () => {
127+
const { content } = compile(`
128+
<script lang="ts" setup>
129+
import { useAttrs, ref} from 'vue'
130+
type CompAttrs = { foo: number }
131+
const attrs = useAttrs<CompAttrs>()
132+
</script>
133+
`)
134+
assertCode(content)
135+
expect(content).toMatch('{ attrs: {} as { foo: number }}')
136+
})
137+
115138
test('defineEmits()', () => {
116139
const { content, bindings } = compile(`
117140
<script setup>

packages/compiler-sfc/src/compileScript.ts

+37-1
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ export function compileScript(
288288
let propsDestructureDecl: Node | undefined
289289
let propsDestructureRestId: string | undefined
290290
let propsTypeDecl: TSTypeLiteral | TSInterfaceBody | undefined
291+
let attrsTypeDecl: TSTypeLiteral | TSInterfaceBody | undefined
291292
let propsTypeDeclRaw: Node | undefined
292293
let propsIdentifier: string | undefined
293294
let emitsRuntimeDecl: Node | undefined
@@ -503,6 +504,33 @@ export function compileScript(
503504
return true
504505
}
505506

507+
function processDefineAttrs(node: Node): boolean {
508+
const USE_ATTRS = 'useAttrs'
509+
if (!isCallOf(node, USE_ATTRS)) {
510+
return false
511+
}
512+
513+
let attrsTypeDeclRaw: Node | undefined
514+
515+
// call has type parameters - infer runtime types from it
516+
if (node.typeParameters) {
517+
attrsTypeDeclRaw = node.typeParameters.params[0]
518+
attrsTypeDecl = resolveQualifiedType(
519+
attrsTypeDeclRaw,
520+
node => node.type === 'TSTypeLiteral'
521+
) as TSTypeLiteral | TSInterfaceBody | undefined
522+
if (!attrsTypeDecl) {
523+
error(
524+
`type argument passed to ${USE_ATTRS}() must be a literal type, ` +
525+
`or a reference to an interface or literal type.`,
526+
attrsTypeDeclRaw
527+
)
528+
}
529+
return true
530+
}
531+
return false
532+
}
533+
506534
function processWithDefaults(
507535
node: Node,
508536
declId?: LVal,
@@ -1213,6 +1241,7 @@ export function compileScript(
12131241
processDefineProps(decl.init, decl.id, node.kind) ||
12141242
processWithDefaults(decl.init, decl.id, node.kind)
12151243
const isDefineEmits = processDefineEmits(decl.init, decl.id)
1244+
processDefineAttrs(decl.init)
12161245
if (isDefineProps || isDefineEmits) {
12171246
if (left === 1) {
12181247
s.remove(node.start! + startOffset, node.end! + startOffset)
@@ -1656,7 +1685,14 @@ export function compileScript(
16561685
hasAwait ? `async ` : ``
16571686
}setup(${args}) {\n${exposeCall}`
16581687
)
1659-
s.appendRight(endOffset, `})`)
1688+
if (attrsTypeDecl) {
1689+
s.appendRight(
1690+
endOffset,
1691+
`}, { attrs: {} as ${genSetupPropsType(attrsTypeDecl)}})`
1692+
)
1693+
} else {
1694+
s.appendRight(endOffset, `})`)
1695+
}
16601696
} else {
16611697
if (defaultExport) {
16621698
// without TS, can't rely on rest spread, so we use Object.assign

0 commit comments

Comments
 (0)