@weapp-vite/volar 2.0.8 → 2.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -36
- package/dist/constants.d.ts +1 -0
- package/dist/index.cjs +332 -53
- package/dist/index.mjs +332 -53
- package/dist/parseSfc.d.ts +9 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,58 +1,72 @@
|
|
|
1
1
|
# @weapp-vite/volar
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## 1. 简介
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
`@weapp-vite/volar` 是 `weapp-vite` 的 Volar 语言服务插件,用来给小程序 Vue SFC 中的自定义块和相关宏提供更准确的类型、补全和诊断能力。
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
它主要覆盖:
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- `<json>` 自定义块的 schema 与类型提示
|
|
10
|
+
- `definePageJson` / `defineComponentJson` 等配置宏配套体验
|
|
11
|
+
- `wxs` 模块声明补全
|
|
12
|
+
- `script setup` 中为 weapp-vite / wevu 注入的额外声明
|
|
13
|
+
|
|
14
|
+
## 2. 安装方式
|
|
15
|
+
|
|
16
|
+
大多数情况下不需要单独安装。
|
|
17
|
+
|
|
18
|
+
`weapp-vite` 已经依赖并自动启用这个插件,项目里通常只需要按文档配置 `tsconfig.json` / `jsconfig.json` 即可。
|
|
19
|
+
|
|
20
|
+
如果你要在非标准工程里单独集成,可以安装:
|
|
10
21
|
|
|
11
22
|
```bash
|
|
12
|
-
|
|
13
|
-
# or
|
|
14
|
-
pnpm add -D @weapp-vite/volar
|
|
15
|
-
# or
|
|
16
|
-
yarn add -D @weapp-vite/volar
|
|
23
|
+
pnpm add -D @weapp-vite/volar @weapp-core/schematics
|
|
17
24
|
```
|
|
18
25
|
|
|
19
|
-
##
|
|
20
|
-
|
|
21
|
-
**推荐方式:** 通过 weapp-vite 使用,插件会自动启用。
|
|
26
|
+
## 3. 常见配置
|
|
22
27
|
|
|
23
|
-
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"vueCompilerOptions": {
|
|
31
|
+
"plugins": ["weapp-vite/volar"]
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
```
|
|
24
35
|
|
|
25
|
-
|
|
36
|
+
> **注意**:更完整的编辑器接入方式,请优先参考 `weapp-vite` 主包文档中的 Volar 章节。
|
|
26
37
|
|
|
27
|
-
|
|
28
|
-
- ✅ **JSON Schema 支持** - 支持 JSON Schema 验证和自动补全
|
|
29
|
-
- ✅ **TypeScript 类型检查** - 利用 TypeScript 类型系统确保配置正确性
|
|
30
|
-
- ✅ **自动推断配置类型** - 根据文件路径自动推断是 App/Page/Component 配置
|
|
31
|
-
- ✅ **多模式支持** - 支持 JSON 模式和 TypeScript 模式
|
|
38
|
+
## 4. 提供的能力
|
|
32
39
|
|
|
33
|
-
|
|
40
|
+
| 能力 | 说明 |
|
|
41
|
+
| ----------------------- | ------------------------------------------------------ |
|
|
42
|
+
| `<json>` 块诊断 | 根据 App / Page / Component 语义提供 schema 与类型校验 |
|
|
43
|
+
| `script setup` 声明增强 | 自动补齐 weapp 相关额外声明 |
|
|
44
|
+
| `wxs` 模块名收集 | 让模板与脚本中的 `wxs` 使用获得更稳定的类型体验 |
|
|
45
|
+
| 自定义块嵌入代码 | 让 Volar 能识别并处理额外代码片段 |
|
|
34
46
|
|
|
35
|
-
|
|
36
|
-
# 安装依赖
|
|
37
|
-
pnpm install
|
|
47
|
+
## 5. 适用场景
|
|
38
48
|
|
|
39
|
-
|
|
40
|
-
|
|
49
|
+
- 在 `.vue` 文件里直接编写小程序页面 / 组件配置
|
|
50
|
+
- 使用 `definePageJson`、`defineComponentJson` 等宏
|
|
51
|
+
- 需要让 VS Code / Volar 正确理解 weapp-vite 的自定义块与扩展语义
|
|
41
52
|
|
|
42
|
-
|
|
43
|
-
pnpm build
|
|
53
|
+
## 6. 本地开发
|
|
44
54
|
|
|
45
|
-
|
|
46
|
-
pnpm
|
|
55
|
+
```bash
|
|
56
|
+
pnpm --filter @weapp-vite/volar build
|
|
57
|
+
pnpm --filter @weapp-vite/volar test
|
|
58
|
+
pnpm --filter @weapp-vite/volar typecheck
|
|
59
|
+
pnpm --filter @weapp-vite/volar benchmark
|
|
47
60
|
```
|
|
48
61
|
|
|
49
|
-
|
|
62
|
+
benchmark 会为普通 SFC 和 `defineOptions` SFC 创建全新的 content 与 snapshot,并强制生成 service script 和全部 embedded code。对比修改前构建时可传入基线插件:
|
|
50
63
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
- [微信小程序官方文档](https://developers.weixin.qq.com/miniprogram/dev/framework/)
|
|
64
|
+
```bash
|
|
65
|
+
pnpm --filter @weapp-vite/volar benchmark -- --baseline /path/to/baseline/dist/index.mjs
|
|
66
|
+
```
|
|
55
67
|
|
|
56
|
-
##
|
|
68
|
+
## 7. 相关链接
|
|
57
69
|
|
|
58
|
-
|
|
70
|
+
- 详细用法:[./USAGE.md](./USAGE.md)
|
|
71
|
+
- 主包文档:[../weapp-vite/docs/volar.md](../weapp-vite/docs/volar.md)
|
|
72
|
+
- `weapp-vite`:[../weapp-vite/README.md](../weapp-vite/README.md)
|
package/dist/constants.d.ts
CHANGED
package/dist/index.cjs
CHANGED
|
@@ -232,6 +232,157 @@ function resolveEmbeddedJsonBlock(fileName, sfc, embeddedCode, tsModule, hasSche
|
|
|
232
232
|
]);
|
|
233
233
|
}
|
|
234
234
|
//#endregion
|
|
235
|
+
//#region src/parseSfc.ts
|
|
236
|
+
function parseAttr(property, node) {
|
|
237
|
+
if (!property.value) return true;
|
|
238
|
+
const { source, start } = property.value.loc;
|
|
239
|
+
const quote = source[0];
|
|
240
|
+
const quoted = (quote === "\"" || quote === "'") && source.endsWith(quote);
|
|
241
|
+
return {
|
|
242
|
+
text: quoted ? source.slice(1, -1) : source,
|
|
243
|
+
offset: (quoted ? start.offset + 1 : start.offset) - node.loc.start.offset
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function repairIncompleteTemplate(descriptor, errors) {
|
|
247
|
+
const template = descriptor.template;
|
|
248
|
+
if (!template) return;
|
|
249
|
+
for (const error of errors) {
|
|
250
|
+
if (!("code" in error) || error.code !== 24 || error.loc?.start.line !== template.loc.start.line) continue;
|
|
251
|
+
const endTagOffset = template.content.lastIndexOf("<");
|
|
252
|
+
const endTagText = template.content.slice(endTagOffset).trimEnd();
|
|
253
|
+
if ("</template>".startsWith(endTagText)) {
|
|
254
|
+
template.loc.end.offset = template.loc.start.offset + endTagOffset;
|
|
255
|
+
template.content = template.content.slice(0, endTagOffset);
|
|
256
|
+
template.loc.source = template.content;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
function createBlock(compilerDom, node, source) {
|
|
261
|
+
const type = node.tag;
|
|
262
|
+
let { start, end } = node.loc;
|
|
263
|
+
let content = "";
|
|
264
|
+
if (node.children.length) {
|
|
265
|
+
start = node.children[0].loc.start;
|
|
266
|
+
end = node.children[node.children.length - 1].loc.end;
|
|
267
|
+
content = source.slice(start.offset, end.offset);
|
|
268
|
+
} else {
|
|
269
|
+
const offset = node.loc.source.indexOf("</");
|
|
270
|
+
if (offset > -1) start = {
|
|
271
|
+
line: start.line,
|
|
272
|
+
column: start.column + offset,
|
|
273
|
+
offset: start.offset + offset
|
|
274
|
+
};
|
|
275
|
+
end = { ...start };
|
|
276
|
+
}
|
|
277
|
+
const attrs = {};
|
|
278
|
+
const block = {
|
|
279
|
+
type,
|
|
280
|
+
content,
|
|
281
|
+
loc: {
|
|
282
|
+
source: content,
|
|
283
|
+
start,
|
|
284
|
+
end
|
|
285
|
+
},
|
|
286
|
+
attrs
|
|
287
|
+
};
|
|
288
|
+
for (const property of node.props) {
|
|
289
|
+
if (property.type !== compilerDom.NodeTypes.ATTRIBUTE) continue;
|
|
290
|
+
attrs[property.name] = property.value ? property.value.content || true : true;
|
|
291
|
+
if (property.name === "lang") block.lang = property.value?.content;
|
|
292
|
+
else if (property.name === "src") block.__src = parseAttr(property, node);
|
|
293
|
+
else if (type === "script") {
|
|
294
|
+
const scriptBlock = block;
|
|
295
|
+
if (property.name === "vapor") {
|
|
296
|
+
scriptBlock.setup ??= attrs[property.name];
|
|
297
|
+
scriptBlock.__generic ??= true;
|
|
298
|
+
} else if (property.name === "setup") scriptBlock.setup = attrs[property.name];
|
|
299
|
+
else if (property.name === "generic") scriptBlock.__generic = parseAttr(property, node);
|
|
300
|
+
} else if (type === "style") {
|
|
301
|
+
const styleBlock = block;
|
|
302
|
+
if (property.name === "scoped") styleBlock.scoped = true;
|
|
303
|
+
else if (property.name === "module") styleBlock.__module = parseAttr(property, node);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return block;
|
|
307
|
+
}
|
|
308
|
+
function parseSfc(compilerDom, source, filename) {
|
|
309
|
+
const errors = [];
|
|
310
|
+
const ast = compilerDom.parse(source, {
|
|
311
|
+
isNativeTag: () => true,
|
|
312
|
+
isPreTag: () => true,
|
|
313
|
+
parseMode: "sfc",
|
|
314
|
+
onError: (error) => errors.push(error),
|
|
315
|
+
comments: true
|
|
316
|
+
});
|
|
317
|
+
const descriptor = {
|
|
318
|
+
filename,
|
|
319
|
+
source,
|
|
320
|
+
comments: [],
|
|
321
|
+
template: null,
|
|
322
|
+
script: null,
|
|
323
|
+
scriptSetup: null,
|
|
324
|
+
styles: [],
|
|
325
|
+
customBlocks: [],
|
|
326
|
+
cssVars: [],
|
|
327
|
+
slotted: false,
|
|
328
|
+
shouldForceReload: () => false
|
|
329
|
+
};
|
|
330
|
+
for (const node of ast.children) {
|
|
331
|
+
if (node.type === compilerDom.NodeTypes.COMMENT) {
|
|
332
|
+
descriptor.comments.push(node.content);
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (node.type !== compilerDom.NodeTypes.ELEMENT) continue;
|
|
336
|
+
if (node.tag === "template") {
|
|
337
|
+
descriptor.template = createBlock(compilerDom, node, source);
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
if (node.tag === "script") {
|
|
341
|
+
const scriptBlock = createBlock(compilerDom, node, source);
|
|
342
|
+
if (scriptBlock.setup && !descriptor.scriptSetup) descriptor.scriptSetup = scriptBlock;
|
|
343
|
+
else if (!scriptBlock.setup && !descriptor.script) descriptor.script = scriptBlock;
|
|
344
|
+
continue;
|
|
345
|
+
}
|
|
346
|
+
if (node.tag === "style") {
|
|
347
|
+
descriptor.styles.push(createBlock(compilerDom, node, source));
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
descriptor.customBlocks.push(createBlock(compilerDom, node, source));
|
|
351
|
+
}
|
|
352
|
+
repairIncompleteTemplate(descriptor, errors);
|
|
353
|
+
return {
|
|
354
|
+
descriptor,
|
|
355
|
+
errors
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function updateSfc(sfc, change) {
|
|
359
|
+
const blocks = [
|
|
360
|
+
sfc.descriptor.template,
|
|
361
|
+
sfc.descriptor.script,
|
|
362
|
+
sfc.descriptor.scriptSetup,
|
|
363
|
+
...sfc.descriptor.styles,
|
|
364
|
+
...sfc.descriptor.customBlocks
|
|
365
|
+
].filter((block) => block !== null);
|
|
366
|
+
const hitBlock = blocks.find((block) => change.start >= block.loc.start.offset && change.end <= block.loc.end.offset);
|
|
367
|
+
if (!hitBlock) return;
|
|
368
|
+
const oldContent = hitBlock.content;
|
|
369
|
+
const relativeStart = change.start - hitBlock.loc.start.offset;
|
|
370
|
+
const relativeEnd = change.end - hitBlock.loc.start.offset;
|
|
371
|
+
const newContent = oldContent.slice(0, relativeStart) + change.newText + oldContent.slice(relativeEnd);
|
|
372
|
+
const endTagRE = new RegExp(`</\\s*${hitBlock.type}\\s*>`);
|
|
373
|
+
if (endTagRE.test(oldContent) !== endTagRE.test(newContent)) return;
|
|
374
|
+
hitBlock.content = newContent;
|
|
375
|
+
hitBlock.loc.source = newContent;
|
|
376
|
+
const lengthDiff = change.newText.length - (change.end - change.start);
|
|
377
|
+
for (const block of blocks) {
|
|
378
|
+
if (block.loc.start.offset > change.end) block.loc.start.offset += lengthDiff;
|
|
379
|
+
if (block.loc.end.offset >= change.end) block.loc.end.offset += lengthDiff;
|
|
380
|
+
}
|
|
381
|
+
const source = sfc.descriptor.source;
|
|
382
|
+
sfc.descriptor.source = source.slice(0, change.start) + change.newText + source.slice(change.end);
|
|
383
|
+
return sfc;
|
|
384
|
+
}
|
|
385
|
+
//#endregion
|
|
235
386
|
//#region src/scriptSetup.ts
|
|
236
387
|
function collectWxsModuleNames(templateContent) {
|
|
237
388
|
if (!templateContent) return [];
|
|
@@ -302,9 +453,7 @@ function collectBindingNames(name, tsModule, bindings) {
|
|
|
302
453
|
}
|
|
303
454
|
for (const element of name.elements) if (tsModule.isBindingElement(element)) collectBindingNames(element.name, tsModule, bindings);
|
|
304
455
|
}
|
|
305
|
-
function collectTopLevelBindingNames(
|
|
306
|
-
const scriptKind = lang === "ts" ? 3 : 1;
|
|
307
|
-
const sourceFile = tsModule.createSourceFile(`bindings.${lang}`, code, 99, true, scriptKind);
|
|
456
|
+
function collectTopLevelBindingNames(sourceFile, tsModule) {
|
|
308
457
|
const bindings = /* @__PURE__ */ new Set();
|
|
309
458
|
for (const statement of sourceFile.statements) {
|
|
310
459
|
if (tsModule.isVariableStatement(statement)) {
|
|
@@ -329,15 +478,18 @@ function unwrapParenthesizedExpression(node, tsModule) {
|
|
|
329
478
|
while (tsModule.isParenthesizedExpression(current)) current = current.expression;
|
|
330
479
|
return current;
|
|
331
480
|
}
|
|
332
|
-
function
|
|
481
|
+
function findReturnedExpression(node, tsModule) {
|
|
333
482
|
if (tsModule.isObjectLiteralExpression(node)) return node;
|
|
334
|
-
if (!tsModule.isBlock(node)) return;
|
|
483
|
+
if (!tsModule.isBlock(node)) return node;
|
|
335
484
|
for (const statement of node.statements) {
|
|
336
485
|
if (!tsModule.isReturnStatement(statement) || !statement.expression) continue;
|
|
337
|
-
|
|
338
|
-
if (tsModule.isObjectLiteralExpression(expression)) return expression;
|
|
486
|
+
return unwrapParenthesizedExpression(statement.expression, tsModule);
|
|
339
487
|
}
|
|
340
488
|
}
|
|
489
|
+
function findReturnedObjectLiteral(node, tsModule) {
|
|
490
|
+
const expression = findReturnedExpression(node, tsModule);
|
|
491
|
+
return expression && tsModule.isObjectLiteralExpression(expression) ? expression : void 0;
|
|
492
|
+
}
|
|
341
493
|
function extractDefineOptionsObjectLiteral(node, tsModule) {
|
|
342
494
|
if (!tsModule.isIdentifier(node.expression) || node.expression.text !== "defineOptions") return;
|
|
343
495
|
const [firstArg] = node.arguments;
|
|
@@ -346,20 +498,88 @@ function extractDefineOptionsObjectLiteral(node, tsModule) {
|
|
|
346
498
|
if (tsModule.isObjectLiteralExpression(arg)) return arg;
|
|
347
499
|
if (tsModule.isArrowFunction(arg) || tsModule.isFunctionExpression(arg)) return findReturnedObjectLiteral(arg.body, tsModule);
|
|
348
500
|
}
|
|
349
|
-
function
|
|
350
|
-
|
|
501
|
+
function setBindingType(map, name, typeText) {
|
|
502
|
+
if (!map.has(name)) map.set(name, typeText);
|
|
503
|
+
}
|
|
504
|
+
function getRuntimeConstructorType(node, tsModule) {
|
|
505
|
+
if (tsModule.isIdentifier(node)) {
|
|
506
|
+
if (node.text === "String") return "string";
|
|
507
|
+
if (node.text === "Number") return "number";
|
|
508
|
+
if (node.text === "Boolean") return "boolean";
|
|
509
|
+
if (node.text === "Array") return "any[]";
|
|
510
|
+
if (node.text === "Object") return "Record<string, any>";
|
|
511
|
+
}
|
|
512
|
+
if (tsModule.isArrayLiteralExpression(node)) {
|
|
513
|
+
const types = [...new Set(node.elements.map((element) => {
|
|
514
|
+
if (tsModule.isSpreadElement(element)) return null;
|
|
515
|
+
return getRuntimeConstructorType(element, tsModule);
|
|
516
|
+
}).filter((typeText) => Boolean(typeText)))];
|
|
517
|
+
return types.length > 0 ? types.join(" | ") : null;
|
|
518
|
+
}
|
|
519
|
+
return null;
|
|
520
|
+
}
|
|
521
|
+
function getFunctionSignatureTypeText(node, sourceFile) {
|
|
522
|
+
if (!(node.parameters.some((parameter) => Boolean(parameter.type)) || Boolean(node.type))) return "(...args: any[]) => any";
|
|
523
|
+
return `(${node.parameters.map((parameter) => parameter.getText(sourceFile)).join(", ")}) => ${node.type?.getText(sourceFile) ?? "any"}`;
|
|
524
|
+
}
|
|
525
|
+
function getMiniProgramPropertyType(node, tsModule) {
|
|
526
|
+
const runtimeType = getRuntimeConstructorType(node, tsModule);
|
|
527
|
+
if (runtimeType) return runtimeType;
|
|
528
|
+
if (!tsModule.isObjectLiteralExpression(node)) return "any";
|
|
351
529
|
for (const property of node.properties) {
|
|
352
|
-
if (!tsModule.isPropertyAssignment(property)
|
|
353
|
-
|
|
354
|
-
|
|
530
|
+
if (!tsModule.isPropertyAssignment(property)) continue;
|
|
531
|
+
if (getPropertyNameText(property.name, tsModule) !== "type") continue;
|
|
532
|
+
return getRuntimeConstructorType(unwrapParenthesizedExpression(property.initializer, tsModule), tsModule) ?? "any";
|
|
355
533
|
}
|
|
356
|
-
return
|
|
534
|
+
return "any";
|
|
357
535
|
}
|
|
358
|
-
function
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
536
|
+
function getExpressionTypeText(node, tsModule, sourceFile) {
|
|
537
|
+
const expression = unwrapParenthesizedExpression(node, tsModule);
|
|
538
|
+
if (tsModule.isAsExpression(expression) || tsModule.isTypeAssertionExpression(expression)) return expression.type.getText(sourceFile);
|
|
539
|
+
if (tsModule.isStringLiteral(expression) || tsModule.isNoSubstitutionTemplateLiteral(expression)) return "string";
|
|
540
|
+
if (tsModule.isNumericLiteral(expression)) return "number";
|
|
541
|
+
if (expression.kind === tsModule.SyntaxKind.TrueKeyword || expression.kind === tsModule.SyntaxKind.FalseKeyword) return "boolean";
|
|
542
|
+
if (tsModule.isPrefixUnaryExpression(expression) && tsModule.isNumericLiteral(expression.operand)) return "number";
|
|
543
|
+
if (tsModule.isArrayLiteralExpression(expression)) {
|
|
544
|
+
const itemTypes = [...new Set(expression.elements.map((element) => {
|
|
545
|
+
if (tsModule.isSpreadElement(element)) return null;
|
|
546
|
+
return getExpressionTypeText(element, tsModule, sourceFile);
|
|
547
|
+
}).filter((typeText) => Boolean(typeText) && typeText !== "undefined"))];
|
|
548
|
+
if (itemTypes.length === 0) return "any[]";
|
|
549
|
+
return itemTypes.length === 1 ? `${itemTypes[0]}[]` : `(${itemTypes.join(" | ")})[]`;
|
|
550
|
+
}
|
|
551
|
+
if (tsModule.isObjectLiteralExpression(expression)) {
|
|
552
|
+
const fields = [];
|
|
553
|
+
for (const property of expression.properties) {
|
|
554
|
+
if (tsModule.isPropertyAssignment(property)) {
|
|
555
|
+
const name = getPropertyNameText(property.name, tsModule);
|
|
556
|
+
if (name) fields.push(`${name}: ${getExpressionTypeText(property.initializer, tsModule, sourceFile)}`);
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
if (tsModule.isShorthandPropertyAssignment(property)) {
|
|
560
|
+
fields.push(`${property.name.text}: any`);
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
if (tsModule.isMethodDeclaration(property) || tsModule.isGetAccessorDeclaration(property)) {
|
|
564
|
+
const name = getPropertyNameText(property.name, tsModule);
|
|
565
|
+
if (name) fields.push(`${name}: (...args: any[]) => any`);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
return fields.length > 0 ? `{ ${fields.join("; ")} }` : "Record<string, any>";
|
|
569
|
+
}
|
|
570
|
+
if (tsModule.isArrowFunction(expression) || tsModule.isFunctionExpression(expression)) return "(...args: any[]) => any";
|
|
571
|
+
if (tsModule.isIdentifier(expression) && expression.text === "undefined") return "undefined";
|
|
572
|
+
return "any";
|
|
573
|
+
}
|
|
574
|
+
function getFunctionLikeReturnTypeText(node, tsModule, sourceFile) {
|
|
575
|
+
if (node.type) return node.type.getText(sourceFile);
|
|
576
|
+
if (!node.body) return "any";
|
|
577
|
+
const returnedExpression = tsModule.isBlock(node.body) ? findReturnedExpression(node.body, tsModule) : findReturnedExpression(node.body, tsModule);
|
|
578
|
+
return returnedExpression ? getExpressionTypeText(returnedExpression, tsModule, sourceFile) : "any";
|
|
579
|
+
}
|
|
580
|
+
function collectDefineOptionsTemplateBindings(sourceFile, tsModule) {
|
|
581
|
+
const valueBindings = /* @__PURE__ */ new Map();
|
|
582
|
+
const functionBindings = /* @__PURE__ */ new Map();
|
|
363
583
|
const visit = (node) => {
|
|
364
584
|
if (tsModule.isCallExpression(node)) {
|
|
365
585
|
const optionsObject = extractDefineOptionsObjectLiteral(node, tsModule);
|
|
@@ -368,12 +588,31 @@ function collectDefineOptionsTemplateBindings(code, tsModule, lang) {
|
|
|
368
588
|
if (!sectionName) continue;
|
|
369
589
|
if (sectionName === "methods") {
|
|
370
590
|
const methodsObject = tsModule.isPropertyAssignment(property) ? unwrapParenthesizedExpression(property.initializer, tsModule) : void 0;
|
|
371
|
-
if (methodsObject && tsModule.isObjectLiteralExpression(methodsObject)) for (const
|
|
591
|
+
if (methodsObject && tsModule.isObjectLiteralExpression(methodsObject)) for (const entry of methodsObject.properties) {
|
|
592
|
+
if (!tsModule.isPropertyAssignment(entry) && !tsModule.isMethodDeclaration(entry) && !tsModule.isShorthandPropertyAssignment(entry)) continue;
|
|
593
|
+
const name = getPropertyNameText(entry.name, tsModule);
|
|
594
|
+
if (!name || !isIdentifierName(name)) continue;
|
|
595
|
+
const typeText = tsModule.isPropertyAssignment(entry) && (tsModule.isArrowFunction(entry.initializer) || tsModule.isFunctionExpression(entry.initializer)) ? getFunctionSignatureTypeText(entry.initializer, sourceFile) : tsModule.isMethodDeclaration(entry) ? getFunctionSignatureTypeText(entry, sourceFile) : "(...args: any[]) => any";
|
|
596
|
+
setBindingType(functionBindings, name, typeText);
|
|
597
|
+
}
|
|
372
598
|
continue;
|
|
373
599
|
}
|
|
374
600
|
if (sectionName === "properties" || sectionName === "computed") {
|
|
375
601
|
const objectValue = tsModule.isPropertyAssignment(property) ? unwrapParenthesizedExpression(property.initializer, tsModule) : void 0;
|
|
376
|
-
if (objectValue && tsModule.isObjectLiteralExpression(objectValue)) for (const
|
|
602
|
+
if (objectValue && tsModule.isObjectLiteralExpression(objectValue)) for (const entry of objectValue.properties) {
|
|
603
|
+
if (!tsModule.isPropertyAssignment(entry) && !tsModule.isMethodDeclaration(entry) && !tsModule.isShorthandPropertyAssignment(entry) && !tsModule.isGetAccessorDeclaration(entry)) continue;
|
|
604
|
+
const name = getPropertyNameText(entry.name, tsModule);
|
|
605
|
+
if (!name || !isIdentifierName(name)) continue;
|
|
606
|
+
let typeText = "any";
|
|
607
|
+
if (sectionName === "properties" && tsModule.isPropertyAssignment(entry)) typeText = getMiniProgramPropertyType(unwrapParenthesizedExpression(entry.initializer, tsModule), tsModule);
|
|
608
|
+
else if (sectionName === "computed") {
|
|
609
|
+
if (tsModule.isPropertyAssignment(entry)) {
|
|
610
|
+
const initializer = unwrapParenthesizedExpression(entry.initializer, tsModule);
|
|
611
|
+
if (tsModule.isArrowFunction(initializer) || tsModule.isFunctionExpression(initializer)) typeText = getFunctionLikeReturnTypeText(initializer, tsModule, sourceFile);
|
|
612
|
+
} else if (tsModule.isMethodDeclaration(entry) || tsModule.isGetAccessorDeclaration(entry)) typeText = getFunctionLikeReturnTypeText(entry, tsModule, sourceFile);
|
|
613
|
+
}
|
|
614
|
+
setBindingType(valueBindings, name, typeText);
|
|
615
|
+
}
|
|
377
616
|
continue;
|
|
378
617
|
}
|
|
379
618
|
if (sectionName === "data") {
|
|
@@ -381,9 +620,16 @@ function collectDefineOptionsTemplateBindings(code, tsModule, lang) {
|
|
|
381
620
|
if (tsModule.isMethodDeclaration(property)) dataObject = findReturnedObjectLiteral(property.body ?? tsModule.factory.createBlock([], false), tsModule);
|
|
382
621
|
else if (tsModule.isPropertyAssignment(property)) {
|
|
383
622
|
const initializer = unwrapParenthesizedExpression(property.initializer, tsModule);
|
|
384
|
-
if (tsModule.
|
|
623
|
+
if (tsModule.isObjectLiteralExpression(initializer)) dataObject = initializer;
|
|
624
|
+
else if (tsModule.isArrowFunction(initializer) || tsModule.isFunctionExpression(initializer)) dataObject = findReturnedObjectLiteral(initializer.body, tsModule);
|
|
625
|
+
}
|
|
626
|
+
if (dataObject) for (const property of dataObject.properties) {
|
|
627
|
+
if (!tsModule.isPropertyAssignment(property) && !tsModule.isMethodDeclaration(property) && !tsModule.isShorthandPropertyAssignment(property) && !tsModule.isGetAccessorDeclaration(property)) continue;
|
|
628
|
+
const name = getPropertyNameText(property.name, tsModule);
|
|
629
|
+
if (!name || !isIdentifierName(name)) continue;
|
|
630
|
+
const typeText = tsModule.isPropertyAssignment(property) ? getExpressionTypeText(property.initializer, tsModule, sourceFile) : "(...args: any[]) => any";
|
|
631
|
+
setBindingType(valueBindings, name, typeText);
|
|
385
632
|
}
|
|
386
|
-
if (dataObject) for (const name of collectObjectLiteralKeys(dataObject, tsModule)) valueBindings.add(name);
|
|
387
633
|
}
|
|
388
634
|
}
|
|
389
635
|
}
|
|
@@ -396,16 +642,18 @@ function collectDefineOptionsTemplateBindings(code, tsModule, lang) {
|
|
|
396
642
|
};
|
|
397
643
|
}
|
|
398
644
|
function createDefineOptionsTemplateDeclarations(code, tsModule, lang) {
|
|
399
|
-
const
|
|
400
|
-
const
|
|
645
|
+
const scriptKind = lang === "ts" ? 3 : 1;
|
|
646
|
+
const sourceFile = tsModule.createSourceFile(`script-setup.${lang}`, code, 99, true, scriptKind);
|
|
647
|
+
const topLevelBindings = collectTopLevelBindingNames(sourceFile, tsModule);
|
|
648
|
+
const bindings = collectDefineOptionsTemplateBindings(sourceFile, tsModule);
|
|
401
649
|
const declarations = [];
|
|
402
|
-
for (const name of bindings.values) {
|
|
650
|
+
for (const [name, typeText] of bindings.values) {
|
|
403
651
|
if (topLevelBindings.has(name)) continue;
|
|
404
|
-
declarations.push(`const ${name}:
|
|
652
|
+
declarations.push(`const ${name}: ${typeText} = null as any`);
|
|
405
653
|
}
|
|
406
|
-
for (const name of bindings.functions) {
|
|
654
|
+
for (const [name, typeText] of bindings.functions) {
|
|
407
655
|
if (topLevelBindings.has(name)) continue;
|
|
408
|
-
declarations.push(`const ${name}:
|
|
656
|
+
declarations.push(`const ${name}: ${typeText} = null as any`);
|
|
409
657
|
}
|
|
410
658
|
return declarations.join("\n");
|
|
411
659
|
}
|
|
@@ -422,43 +670,74 @@ try {
|
|
|
422
670
|
} catch {
|
|
423
671
|
hasSchematicsTypes = false;
|
|
424
672
|
}
|
|
425
|
-
function parseVueSfc(content, filename = "component.vue") {
|
|
426
|
-
try {
|
|
427
|
-
return require$1("@vue/compiler-sfc").parse(content, { filename });
|
|
428
|
-
} catch {
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
673
|
/**
|
|
433
674
|
* Volar 语言插件:为 weapp 配置块提供类型与 schema 提示。
|
|
434
675
|
*/
|
|
435
676
|
const plugin = (ctx) => {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
677
|
+
const tsModule = ctx?.modules?.typescript;
|
|
678
|
+
const compilerDom = ctx?.modules?.["@vue/compiler-dom"];
|
|
679
|
+
const rawSfcStates = /* @__PURE__ */ new WeakMap();
|
|
680
|
+
const defineOptionsDeclarationsCache = /* @__PURE__ */ new Map();
|
|
681
|
+
function getDefineOptionsDeclarations(code, lang) {
|
|
682
|
+
if (!tsModule) return "";
|
|
683
|
+
const key = `${lang}\0${code}`;
|
|
684
|
+
const cached = defineOptionsDeclarationsCache.get(key);
|
|
685
|
+
if (cached !== void 0) {
|
|
686
|
+
defineOptionsDeclarationsCache.delete(key);
|
|
687
|
+
defineOptionsDeclarationsCache.set(key, cached);
|
|
688
|
+
return cached;
|
|
689
|
+
}
|
|
690
|
+
const declarations = createDefineOptionsTemplateDeclarations(code, tsModule, lang);
|
|
691
|
+
if (defineOptionsDeclarationsCache.size >= 128) {
|
|
692
|
+
const oldestKey = defineOptionsDeclarationsCache.keys().next().value;
|
|
693
|
+
if (oldestKey !== void 0) defineOptionsDeclarationsCache.delete(oldestKey);
|
|
694
|
+
}
|
|
695
|
+
defineOptionsDeclarationsCache.set(key, declarations);
|
|
696
|
+
return declarations;
|
|
697
|
+
}
|
|
698
|
+
function restoreRawSfc(parsed) {
|
|
699
|
+
const state = rawSfcStates.get(parsed);
|
|
700
|
+
const scriptSetup = parsed.descriptor.scriptSetup;
|
|
701
|
+
if (state && !state.hadScriptSetup) parsed.descriptor.scriptSetup = null;
|
|
702
|
+
else if (state && scriptSetup && state.scriptSetupContent !== void 0) {
|
|
703
|
+
scriptSetup.content = state.scriptSetupContent;
|
|
704
|
+
scriptSetup.loc.source = state.scriptSetupSource ?? state.scriptSetupContent;
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
function enhanceSfc(parsed) {
|
|
708
|
+
const descriptor = parsed.descriptor;
|
|
709
|
+
const scriptSetup = descriptor.scriptSetup;
|
|
710
|
+
rawSfcStates.set(parsed, {
|
|
711
|
+
hadScriptSetup: Boolean(scriptSetup),
|
|
712
|
+
scriptSetupContent: scriptSetup?.content,
|
|
713
|
+
scriptSetupSource: scriptSetup?.loc.source
|
|
714
|
+
});
|
|
715
|
+
const wxsModuleNames = collectWxsModuleNames(descriptor.template?.content);
|
|
716
|
+
const scriptSetupLang = resolveScriptSetupLang(scriptSetup?.lang);
|
|
717
|
+
const defineOptionsDeclarations = scriptSetup?.content.includes("defineOptions") ? getDefineOptionsDeclarations(scriptSetup.content, scriptSetupLang) : "";
|
|
718
|
+
if (!wxsModuleNames.length && !defineOptionsDeclarations) return;
|
|
719
|
+
if (scriptSetup) {
|
|
720
|
+
scriptSetup.content = appendScriptSetupDeclarations(appendWxsDeclarations(scriptSetup.content, wxsModuleNames), defineOptionsDeclarations);
|
|
721
|
+
syncScriptBlockSource(scriptSetup);
|
|
722
|
+
} else descriptor.scriptSetup = createSyntheticScriptSetup(wxsModuleNames);
|
|
441
723
|
}
|
|
442
724
|
return {
|
|
443
725
|
name,
|
|
444
726
|
version: PLUGIN_VERSION,
|
|
445
727
|
order: -1,
|
|
446
728
|
parseSFC2(fileName, languageId, content) {
|
|
447
|
-
if (languageId !== "vue") return;
|
|
448
|
-
const parsed =
|
|
449
|
-
|
|
450
|
-
const descriptor = parsed.descriptor;
|
|
451
|
-
const wxsModuleNames = collectWxsModuleNames(descriptor.template?.content);
|
|
452
|
-
const scriptSetup = descriptor.scriptSetup;
|
|
453
|
-
const scriptSetupLang = resolveScriptSetupLang(scriptSetup?.lang);
|
|
454
|
-
const defineOptionsDeclarations = scriptSetup?.content && tsModule ? createDefineOptionsTemplateDeclarations(scriptSetup.content, tsModule, scriptSetupLang) : "";
|
|
455
|
-
if (!wxsModuleNames.length && !defineOptionsDeclarations) return parsed;
|
|
456
|
-
if (descriptor.scriptSetup) {
|
|
457
|
-
descriptor.scriptSetup.content = appendScriptSetupDeclarations(appendWxsDeclarations(descriptor.scriptSetup.content, wxsModuleNames), defineOptionsDeclarations);
|
|
458
|
-
syncScriptBlockSource(descriptor.scriptSetup);
|
|
459
|
-
} else descriptor.scriptSetup = createSyntheticScriptSetup(wxsModuleNames);
|
|
729
|
+
if (languageId !== "vue" || !compilerDom) return;
|
|
730
|
+
const parsed = parseSfc(compilerDom, content, fileName);
|
|
731
|
+
enhanceSfc(parsed);
|
|
460
732
|
return parsed;
|
|
461
733
|
},
|
|
734
|
+
updateSFC(oldResult, change) {
|
|
735
|
+
restoreRawSfc(oldResult);
|
|
736
|
+
const updated = updateSfc(oldResult, change);
|
|
737
|
+
if (!updated) return;
|
|
738
|
+
enhanceSfc(updated);
|
|
739
|
+
return updated;
|
|
740
|
+
},
|
|
462
741
|
getEmbeddedCodes(_, sfc) {
|
|
463
742
|
return getEmbeddedCodesFromCustomBlocks(sfc);
|
|
464
743
|
},
|
package/dist/index.mjs
CHANGED
|
@@ -208,6 +208,157 @@ function resolveEmbeddedJsonBlock(fileName, sfc, embeddedCode, tsModule, hasSche
|
|
|
208
208
|
]);
|
|
209
209
|
}
|
|
210
210
|
//#endregion
|
|
211
|
+
//#region src/parseSfc.ts
|
|
212
|
+
function parseAttr(property, node) {
|
|
213
|
+
if (!property.value) return true;
|
|
214
|
+
const { source, start } = property.value.loc;
|
|
215
|
+
const quote = source[0];
|
|
216
|
+
const quoted = (quote === "\"" || quote === "'") && source.endsWith(quote);
|
|
217
|
+
return {
|
|
218
|
+
text: quoted ? source.slice(1, -1) : source,
|
|
219
|
+
offset: (quoted ? start.offset + 1 : start.offset) - node.loc.start.offset
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
function repairIncompleteTemplate(descriptor, errors) {
|
|
223
|
+
const template = descriptor.template;
|
|
224
|
+
if (!template) return;
|
|
225
|
+
for (const error of errors) {
|
|
226
|
+
if (!("code" in error) || error.code !== 24 || error.loc?.start.line !== template.loc.start.line) continue;
|
|
227
|
+
const endTagOffset = template.content.lastIndexOf("<");
|
|
228
|
+
const endTagText = template.content.slice(endTagOffset).trimEnd();
|
|
229
|
+
if ("</template>".startsWith(endTagText)) {
|
|
230
|
+
template.loc.end.offset = template.loc.start.offset + endTagOffset;
|
|
231
|
+
template.content = template.content.slice(0, endTagOffset);
|
|
232
|
+
template.loc.source = template.content;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function createBlock(compilerDom, node, source) {
|
|
237
|
+
const type = node.tag;
|
|
238
|
+
let { start, end } = node.loc;
|
|
239
|
+
let content = "";
|
|
240
|
+
if (node.children.length) {
|
|
241
|
+
start = node.children[0].loc.start;
|
|
242
|
+
end = node.children[node.children.length - 1].loc.end;
|
|
243
|
+
content = source.slice(start.offset, end.offset);
|
|
244
|
+
} else {
|
|
245
|
+
const offset = node.loc.source.indexOf("</");
|
|
246
|
+
if (offset > -1) start = {
|
|
247
|
+
line: start.line,
|
|
248
|
+
column: start.column + offset,
|
|
249
|
+
offset: start.offset + offset
|
|
250
|
+
};
|
|
251
|
+
end = { ...start };
|
|
252
|
+
}
|
|
253
|
+
const attrs = {};
|
|
254
|
+
const block = {
|
|
255
|
+
type,
|
|
256
|
+
content,
|
|
257
|
+
loc: {
|
|
258
|
+
source: content,
|
|
259
|
+
start,
|
|
260
|
+
end
|
|
261
|
+
},
|
|
262
|
+
attrs
|
|
263
|
+
};
|
|
264
|
+
for (const property of node.props) {
|
|
265
|
+
if (property.type !== compilerDom.NodeTypes.ATTRIBUTE) continue;
|
|
266
|
+
attrs[property.name] = property.value ? property.value.content || true : true;
|
|
267
|
+
if (property.name === "lang") block.lang = property.value?.content;
|
|
268
|
+
else if (property.name === "src") block.__src = parseAttr(property, node);
|
|
269
|
+
else if (type === "script") {
|
|
270
|
+
const scriptBlock = block;
|
|
271
|
+
if (property.name === "vapor") {
|
|
272
|
+
scriptBlock.setup ??= attrs[property.name];
|
|
273
|
+
scriptBlock.__generic ??= true;
|
|
274
|
+
} else if (property.name === "setup") scriptBlock.setup = attrs[property.name];
|
|
275
|
+
else if (property.name === "generic") scriptBlock.__generic = parseAttr(property, node);
|
|
276
|
+
} else if (type === "style") {
|
|
277
|
+
const styleBlock = block;
|
|
278
|
+
if (property.name === "scoped") styleBlock.scoped = true;
|
|
279
|
+
else if (property.name === "module") styleBlock.__module = parseAttr(property, node);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return block;
|
|
283
|
+
}
|
|
284
|
+
function parseSfc(compilerDom, source, filename) {
|
|
285
|
+
const errors = [];
|
|
286
|
+
const ast = compilerDom.parse(source, {
|
|
287
|
+
isNativeTag: () => true,
|
|
288
|
+
isPreTag: () => true,
|
|
289
|
+
parseMode: "sfc",
|
|
290
|
+
onError: (error) => errors.push(error),
|
|
291
|
+
comments: true
|
|
292
|
+
});
|
|
293
|
+
const descriptor = {
|
|
294
|
+
filename,
|
|
295
|
+
source,
|
|
296
|
+
comments: [],
|
|
297
|
+
template: null,
|
|
298
|
+
script: null,
|
|
299
|
+
scriptSetup: null,
|
|
300
|
+
styles: [],
|
|
301
|
+
customBlocks: [],
|
|
302
|
+
cssVars: [],
|
|
303
|
+
slotted: false,
|
|
304
|
+
shouldForceReload: () => false
|
|
305
|
+
};
|
|
306
|
+
for (const node of ast.children) {
|
|
307
|
+
if (node.type === compilerDom.NodeTypes.COMMENT) {
|
|
308
|
+
descriptor.comments.push(node.content);
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
if (node.type !== compilerDom.NodeTypes.ELEMENT) continue;
|
|
312
|
+
if (node.tag === "template") {
|
|
313
|
+
descriptor.template = createBlock(compilerDom, node, source);
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
if (node.tag === "script") {
|
|
317
|
+
const scriptBlock = createBlock(compilerDom, node, source);
|
|
318
|
+
if (scriptBlock.setup && !descriptor.scriptSetup) descriptor.scriptSetup = scriptBlock;
|
|
319
|
+
else if (!scriptBlock.setup && !descriptor.script) descriptor.script = scriptBlock;
|
|
320
|
+
continue;
|
|
321
|
+
}
|
|
322
|
+
if (node.tag === "style") {
|
|
323
|
+
descriptor.styles.push(createBlock(compilerDom, node, source));
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
descriptor.customBlocks.push(createBlock(compilerDom, node, source));
|
|
327
|
+
}
|
|
328
|
+
repairIncompleteTemplate(descriptor, errors);
|
|
329
|
+
return {
|
|
330
|
+
descriptor,
|
|
331
|
+
errors
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
function updateSfc(sfc, change) {
|
|
335
|
+
const blocks = [
|
|
336
|
+
sfc.descriptor.template,
|
|
337
|
+
sfc.descriptor.script,
|
|
338
|
+
sfc.descriptor.scriptSetup,
|
|
339
|
+
...sfc.descriptor.styles,
|
|
340
|
+
...sfc.descriptor.customBlocks
|
|
341
|
+
].filter((block) => block !== null);
|
|
342
|
+
const hitBlock = blocks.find((block) => change.start >= block.loc.start.offset && change.end <= block.loc.end.offset);
|
|
343
|
+
if (!hitBlock) return;
|
|
344
|
+
const oldContent = hitBlock.content;
|
|
345
|
+
const relativeStart = change.start - hitBlock.loc.start.offset;
|
|
346
|
+
const relativeEnd = change.end - hitBlock.loc.start.offset;
|
|
347
|
+
const newContent = oldContent.slice(0, relativeStart) + change.newText + oldContent.slice(relativeEnd);
|
|
348
|
+
const endTagRE = new RegExp(`</\\s*${hitBlock.type}\\s*>`);
|
|
349
|
+
if (endTagRE.test(oldContent) !== endTagRE.test(newContent)) return;
|
|
350
|
+
hitBlock.content = newContent;
|
|
351
|
+
hitBlock.loc.source = newContent;
|
|
352
|
+
const lengthDiff = change.newText.length - (change.end - change.start);
|
|
353
|
+
for (const block of blocks) {
|
|
354
|
+
if (block.loc.start.offset > change.end) block.loc.start.offset += lengthDiff;
|
|
355
|
+
if (block.loc.end.offset >= change.end) block.loc.end.offset += lengthDiff;
|
|
356
|
+
}
|
|
357
|
+
const source = sfc.descriptor.source;
|
|
358
|
+
sfc.descriptor.source = source.slice(0, change.start) + change.newText + source.slice(change.end);
|
|
359
|
+
return sfc;
|
|
360
|
+
}
|
|
361
|
+
//#endregion
|
|
211
362
|
//#region src/scriptSetup.ts
|
|
212
363
|
function collectWxsModuleNames(templateContent) {
|
|
213
364
|
if (!templateContent) return [];
|
|
@@ -278,9 +429,7 @@ function collectBindingNames(name, tsModule, bindings) {
|
|
|
278
429
|
}
|
|
279
430
|
for (const element of name.elements) if (tsModule.isBindingElement(element)) collectBindingNames(element.name, tsModule, bindings);
|
|
280
431
|
}
|
|
281
|
-
function collectTopLevelBindingNames(
|
|
282
|
-
const scriptKind = lang === "ts" ? 3 : 1;
|
|
283
|
-
const sourceFile = tsModule.createSourceFile(`bindings.${lang}`, code, 99, true, scriptKind);
|
|
432
|
+
function collectTopLevelBindingNames(sourceFile, tsModule) {
|
|
284
433
|
const bindings = /* @__PURE__ */ new Set();
|
|
285
434
|
for (const statement of sourceFile.statements) {
|
|
286
435
|
if (tsModule.isVariableStatement(statement)) {
|
|
@@ -305,15 +454,18 @@ function unwrapParenthesizedExpression(node, tsModule) {
|
|
|
305
454
|
while (tsModule.isParenthesizedExpression(current)) current = current.expression;
|
|
306
455
|
return current;
|
|
307
456
|
}
|
|
308
|
-
function
|
|
457
|
+
function findReturnedExpression(node, tsModule) {
|
|
309
458
|
if (tsModule.isObjectLiteralExpression(node)) return node;
|
|
310
|
-
if (!tsModule.isBlock(node)) return;
|
|
459
|
+
if (!tsModule.isBlock(node)) return node;
|
|
311
460
|
for (const statement of node.statements) {
|
|
312
461
|
if (!tsModule.isReturnStatement(statement) || !statement.expression) continue;
|
|
313
|
-
|
|
314
|
-
if (tsModule.isObjectLiteralExpression(expression)) return expression;
|
|
462
|
+
return unwrapParenthesizedExpression(statement.expression, tsModule);
|
|
315
463
|
}
|
|
316
464
|
}
|
|
465
|
+
function findReturnedObjectLiteral(node, tsModule) {
|
|
466
|
+
const expression = findReturnedExpression(node, tsModule);
|
|
467
|
+
return expression && tsModule.isObjectLiteralExpression(expression) ? expression : void 0;
|
|
468
|
+
}
|
|
317
469
|
function extractDefineOptionsObjectLiteral(node, tsModule) {
|
|
318
470
|
if (!tsModule.isIdentifier(node.expression) || node.expression.text !== "defineOptions") return;
|
|
319
471
|
const [firstArg] = node.arguments;
|
|
@@ -322,20 +474,88 @@ function extractDefineOptionsObjectLiteral(node, tsModule) {
|
|
|
322
474
|
if (tsModule.isObjectLiteralExpression(arg)) return arg;
|
|
323
475
|
if (tsModule.isArrowFunction(arg) || tsModule.isFunctionExpression(arg)) return findReturnedObjectLiteral(arg.body, tsModule);
|
|
324
476
|
}
|
|
325
|
-
function
|
|
326
|
-
|
|
477
|
+
function setBindingType(map, name, typeText) {
|
|
478
|
+
if (!map.has(name)) map.set(name, typeText);
|
|
479
|
+
}
|
|
480
|
+
function getRuntimeConstructorType(node, tsModule) {
|
|
481
|
+
if (tsModule.isIdentifier(node)) {
|
|
482
|
+
if (node.text === "String") return "string";
|
|
483
|
+
if (node.text === "Number") return "number";
|
|
484
|
+
if (node.text === "Boolean") return "boolean";
|
|
485
|
+
if (node.text === "Array") return "any[]";
|
|
486
|
+
if (node.text === "Object") return "Record<string, any>";
|
|
487
|
+
}
|
|
488
|
+
if (tsModule.isArrayLiteralExpression(node)) {
|
|
489
|
+
const types = [...new Set(node.elements.map((element) => {
|
|
490
|
+
if (tsModule.isSpreadElement(element)) return null;
|
|
491
|
+
return getRuntimeConstructorType(element, tsModule);
|
|
492
|
+
}).filter((typeText) => Boolean(typeText)))];
|
|
493
|
+
return types.length > 0 ? types.join(" | ") : null;
|
|
494
|
+
}
|
|
495
|
+
return null;
|
|
496
|
+
}
|
|
497
|
+
function getFunctionSignatureTypeText(node, sourceFile) {
|
|
498
|
+
if (!(node.parameters.some((parameter) => Boolean(parameter.type)) || Boolean(node.type))) return "(...args: any[]) => any";
|
|
499
|
+
return `(${node.parameters.map((parameter) => parameter.getText(sourceFile)).join(", ")}) => ${node.type?.getText(sourceFile) ?? "any"}`;
|
|
500
|
+
}
|
|
501
|
+
function getMiniProgramPropertyType(node, tsModule) {
|
|
502
|
+
const runtimeType = getRuntimeConstructorType(node, tsModule);
|
|
503
|
+
if (runtimeType) return runtimeType;
|
|
504
|
+
if (!tsModule.isObjectLiteralExpression(node)) return "any";
|
|
327
505
|
for (const property of node.properties) {
|
|
328
|
-
if (!tsModule.isPropertyAssignment(property)
|
|
329
|
-
|
|
330
|
-
|
|
506
|
+
if (!tsModule.isPropertyAssignment(property)) continue;
|
|
507
|
+
if (getPropertyNameText(property.name, tsModule) !== "type") continue;
|
|
508
|
+
return getRuntimeConstructorType(unwrapParenthesizedExpression(property.initializer, tsModule), tsModule) ?? "any";
|
|
331
509
|
}
|
|
332
|
-
return
|
|
510
|
+
return "any";
|
|
333
511
|
}
|
|
334
|
-
function
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
512
|
+
function getExpressionTypeText(node, tsModule, sourceFile) {
|
|
513
|
+
const expression = unwrapParenthesizedExpression(node, tsModule);
|
|
514
|
+
if (tsModule.isAsExpression(expression) || tsModule.isTypeAssertionExpression(expression)) return expression.type.getText(sourceFile);
|
|
515
|
+
if (tsModule.isStringLiteral(expression) || tsModule.isNoSubstitutionTemplateLiteral(expression)) return "string";
|
|
516
|
+
if (tsModule.isNumericLiteral(expression)) return "number";
|
|
517
|
+
if (expression.kind === tsModule.SyntaxKind.TrueKeyword || expression.kind === tsModule.SyntaxKind.FalseKeyword) return "boolean";
|
|
518
|
+
if (tsModule.isPrefixUnaryExpression(expression) && tsModule.isNumericLiteral(expression.operand)) return "number";
|
|
519
|
+
if (tsModule.isArrayLiteralExpression(expression)) {
|
|
520
|
+
const itemTypes = [...new Set(expression.elements.map((element) => {
|
|
521
|
+
if (tsModule.isSpreadElement(element)) return null;
|
|
522
|
+
return getExpressionTypeText(element, tsModule, sourceFile);
|
|
523
|
+
}).filter((typeText) => Boolean(typeText) && typeText !== "undefined"))];
|
|
524
|
+
if (itemTypes.length === 0) return "any[]";
|
|
525
|
+
return itemTypes.length === 1 ? `${itemTypes[0]}[]` : `(${itemTypes.join(" | ")})[]`;
|
|
526
|
+
}
|
|
527
|
+
if (tsModule.isObjectLiteralExpression(expression)) {
|
|
528
|
+
const fields = [];
|
|
529
|
+
for (const property of expression.properties) {
|
|
530
|
+
if (tsModule.isPropertyAssignment(property)) {
|
|
531
|
+
const name = getPropertyNameText(property.name, tsModule);
|
|
532
|
+
if (name) fields.push(`${name}: ${getExpressionTypeText(property.initializer, tsModule, sourceFile)}`);
|
|
533
|
+
continue;
|
|
534
|
+
}
|
|
535
|
+
if (tsModule.isShorthandPropertyAssignment(property)) {
|
|
536
|
+
fields.push(`${property.name.text}: any`);
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
if (tsModule.isMethodDeclaration(property) || tsModule.isGetAccessorDeclaration(property)) {
|
|
540
|
+
const name = getPropertyNameText(property.name, tsModule);
|
|
541
|
+
if (name) fields.push(`${name}: (...args: any[]) => any`);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
return fields.length > 0 ? `{ ${fields.join("; ")} }` : "Record<string, any>";
|
|
545
|
+
}
|
|
546
|
+
if (tsModule.isArrowFunction(expression) || tsModule.isFunctionExpression(expression)) return "(...args: any[]) => any";
|
|
547
|
+
if (tsModule.isIdentifier(expression) && expression.text === "undefined") return "undefined";
|
|
548
|
+
return "any";
|
|
549
|
+
}
|
|
550
|
+
function getFunctionLikeReturnTypeText(node, tsModule, sourceFile) {
|
|
551
|
+
if (node.type) return node.type.getText(sourceFile);
|
|
552
|
+
if (!node.body) return "any";
|
|
553
|
+
const returnedExpression = tsModule.isBlock(node.body) ? findReturnedExpression(node.body, tsModule) : findReturnedExpression(node.body, tsModule);
|
|
554
|
+
return returnedExpression ? getExpressionTypeText(returnedExpression, tsModule, sourceFile) : "any";
|
|
555
|
+
}
|
|
556
|
+
function collectDefineOptionsTemplateBindings(sourceFile, tsModule) {
|
|
557
|
+
const valueBindings = /* @__PURE__ */ new Map();
|
|
558
|
+
const functionBindings = /* @__PURE__ */ new Map();
|
|
339
559
|
const visit = (node) => {
|
|
340
560
|
if (tsModule.isCallExpression(node)) {
|
|
341
561
|
const optionsObject = extractDefineOptionsObjectLiteral(node, tsModule);
|
|
@@ -344,12 +564,31 @@ function collectDefineOptionsTemplateBindings(code, tsModule, lang) {
|
|
|
344
564
|
if (!sectionName) continue;
|
|
345
565
|
if (sectionName === "methods") {
|
|
346
566
|
const methodsObject = tsModule.isPropertyAssignment(property) ? unwrapParenthesizedExpression(property.initializer, tsModule) : void 0;
|
|
347
|
-
if (methodsObject && tsModule.isObjectLiteralExpression(methodsObject)) for (const
|
|
567
|
+
if (methodsObject && tsModule.isObjectLiteralExpression(methodsObject)) for (const entry of methodsObject.properties) {
|
|
568
|
+
if (!tsModule.isPropertyAssignment(entry) && !tsModule.isMethodDeclaration(entry) && !tsModule.isShorthandPropertyAssignment(entry)) continue;
|
|
569
|
+
const name = getPropertyNameText(entry.name, tsModule);
|
|
570
|
+
if (!name || !isIdentifierName(name)) continue;
|
|
571
|
+
const typeText = tsModule.isPropertyAssignment(entry) && (tsModule.isArrowFunction(entry.initializer) || tsModule.isFunctionExpression(entry.initializer)) ? getFunctionSignatureTypeText(entry.initializer, sourceFile) : tsModule.isMethodDeclaration(entry) ? getFunctionSignatureTypeText(entry, sourceFile) : "(...args: any[]) => any";
|
|
572
|
+
setBindingType(functionBindings, name, typeText);
|
|
573
|
+
}
|
|
348
574
|
continue;
|
|
349
575
|
}
|
|
350
576
|
if (sectionName === "properties" || sectionName === "computed") {
|
|
351
577
|
const objectValue = tsModule.isPropertyAssignment(property) ? unwrapParenthesizedExpression(property.initializer, tsModule) : void 0;
|
|
352
|
-
if (objectValue && tsModule.isObjectLiteralExpression(objectValue)) for (const
|
|
578
|
+
if (objectValue && tsModule.isObjectLiteralExpression(objectValue)) for (const entry of objectValue.properties) {
|
|
579
|
+
if (!tsModule.isPropertyAssignment(entry) && !tsModule.isMethodDeclaration(entry) && !tsModule.isShorthandPropertyAssignment(entry) && !tsModule.isGetAccessorDeclaration(entry)) continue;
|
|
580
|
+
const name = getPropertyNameText(entry.name, tsModule);
|
|
581
|
+
if (!name || !isIdentifierName(name)) continue;
|
|
582
|
+
let typeText = "any";
|
|
583
|
+
if (sectionName === "properties" && tsModule.isPropertyAssignment(entry)) typeText = getMiniProgramPropertyType(unwrapParenthesizedExpression(entry.initializer, tsModule), tsModule);
|
|
584
|
+
else if (sectionName === "computed") {
|
|
585
|
+
if (tsModule.isPropertyAssignment(entry)) {
|
|
586
|
+
const initializer = unwrapParenthesizedExpression(entry.initializer, tsModule);
|
|
587
|
+
if (tsModule.isArrowFunction(initializer) || tsModule.isFunctionExpression(initializer)) typeText = getFunctionLikeReturnTypeText(initializer, tsModule, sourceFile);
|
|
588
|
+
} else if (tsModule.isMethodDeclaration(entry) || tsModule.isGetAccessorDeclaration(entry)) typeText = getFunctionLikeReturnTypeText(entry, tsModule, sourceFile);
|
|
589
|
+
}
|
|
590
|
+
setBindingType(valueBindings, name, typeText);
|
|
591
|
+
}
|
|
353
592
|
continue;
|
|
354
593
|
}
|
|
355
594
|
if (sectionName === "data") {
|
|
@@ -357,9 +596,16 @@ function collectDefineOptionsTemplateBindings(code, tsModule, lang) {
|
|
|
357
596
|
if (tsModule.isMethodDeclaration(property)) dataObject = findReturnedObjectLiteral(property.body ?? tsModule.factory.createBlock([], false), tsModule);
|
|
358
597
|
else if (tsModule.isPropertyAssignment(property)) {
|
|
359
598
|
const initializer = unwrapParenthesizedExpression(property.initializer, tsModule);
|
|
360
|
-
if (tsModule.
|
|
599
|
+
if (tsModule.isObjectLiteralExpression(initializer)) dataObject = initializer;
|
|
600
|
+
else if (tsModule.isArrowFunction(initializer) || tsModule.isFunctionExpression(initializer)) dataObject = findReturnedObjectLiteral(initializer.body, tsModule);
|
|
601
|
+
}
|
|
602
|
+
if (dataObject) for (const property of dataObject.properties) {
|
|
603
|
+
if (!tsModule.isPropertyAssignment(property) && !tsModule.isMethodDeclaration(property) && !tsModule.isShorthandPropertyAssignment(property) && !tsModule.isGetAccessorDeclaration(property)) continue;
|
|
604
|
+
const name = getPropertyNameText(property.name, tsModule);
|
|
605
|
+
if (!name || !isIdentifierName(name)) continue;
|
|
606
|
+
const typeText = tsModule.isPropertyAssignment(property) ? getExpressionTypeText(property.initializer, tsModule, sourceFile) : "(...args: any[]) => any";
|
|
607
|
+
setBindingType(valueBindings, name, typeText);
|
|
361
608
|
}
|
|
362
|
-
if (dataObject) for (const name of collectObjectLiteralKeys(dataObject, tsModule)) valueBindings.add(name);
|
|
363
609
|
}
|
|
364
610
|
}
|
|
365
611
|
}
|
|
@@ -372,16 +618,18 @@ function collectDefineOptionsTemplateBindings(code, tsModule, lang) {
|
|
|
372
618
|
};
|
|
373
619
|
}
|
|
374
620
|
function createDefineOptionsTemplateDeclarations(code, tsModule, lang) {
|
|
375
|
-
const
|
|
376
|
-
const
|
|
621
|
+
const scriptKind = lang === "ts" ? 3 : 1;
|
|
622
|
+
const sourceFile = tsModule.createSourceFile(`script-setup.${lang}`, code, 99, true, scriptKind);
|
|
623
|
+
const topLevelBindings = collectTopLevelBindingNames(sourceFile, tsModule);
|
|
624
|
+
const bindings = collectDefineOptionsTemplateBindings(sourceFile, tsModule);
|
|
377
625
|
const declarations = [];
|
|
378
|
-
for (const name of bindings.values) {
|
|
626
|
+
for (const [name, typeText] of bindings.values) {
|
|
379
627
|
if (topLevelBindings.has(name)) continue;
|
|
380
|
-
declarations.push(`const ${name}:
|
|
628
|
+
declarations.push(`const ${name}: ${typeText} = null as any`);
|
|
381
629
|
}
|
|
382
|
-
for (const name of bindings.functions) {
|
|
630
|
+
for (const [name, typeText] of bindings.functions) {
|
|
383
631
|
if (topLevelBindings.has(name)) continue;
|
|
384
|
-
declarations.push(`const ${name}:
|
|
632
|
+
declarations.push(`const ${name}: ${typeText} = null as any`);
|
|
385
633
|
}
|
|
386
634
|
return declarations.join("\n");
|
|
387
635
|
}
|
|
@@ -398,43 +646,74 @@ try {
|
|
|
398
646
|
} catch {
|
|
399
647
|
hasSchematicsTypes = false;
|
|
400
648
|
}
|
|
401
|
-
function parseVueSfc(content, filename = "component.vue") {
|
|
402
|
-
try {
|
|
403
|
-
return require("@vue/compiler-sfc").parse(content, { filename });
|
|
404
|
-
} catch {
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
649
|
/**
|
|
409
650
|
* Volar 语言插件:为 weapp 配置块提供类型与 schema 提示。
|
|
410
651
|
*/
|
|
411
652
|
const plugin = (ctx) => {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
653
|
+
const tsModule = ctx?.modules?.typescript;
|
|
654
|
+
const compilerDom = ctx?.modules?.["@vue/compiler-dom"];
|
|
655
|
+
const rawSfcStates = /* @__PURE__ */ new WeakMap();
|
|
656
|
+
const defineOptionsDeclarationsCache = /* @__PURE__ */ new Map();
|
|
657
|
+
function getDefineOptionsDeclarations(code, lang) {
|
|
658
|
+
if (!tsModule) return "";
|
|
659
|
+
const key = `${lang}\0${code}`;
|
|
660
|
+
const cached = defineOptionsDeclarationsCache.get(key);
|
|
661
|
+
if (cached !== void 0) {
|
|
662
|
+
defineOptionsDeclarationsCache.delete(key);
|
|
663
|
+
defineOptionsDeclarationsCache.set(key, cached);
|
|
664
|
+
return cached;
|
|
665
|
+
}
|
|
666
|
+
const declarations = createDefineOptionsTemplateDeclarations(code, tsModule, lang);
|
|
667
|
+
if (defineOptionsDeclarationsCache.size >= 128) {
|
|
668
|
+
const oldestKey = defineOptionsDeclarationsCache.keys().next().value;
|
|
669
|
+
if (oldestKey !== void 0) defineOptionsDeclarationsCache.delete(oldestKey);
|
|
670
|
+
}
|
|
671
|
+
defineOptionsDeclarationsCache.set(key, declarations);
|
|
672
|
+
return declarations;
|
|
673
|
+
}
|
|
674
|
+
function restoreRawSfc(parsed) {
|
|
675
|
+
const state = rawSfcStates.get(parsed);
|
|
676
|
+
const scriptSetup = parsed.descriptor.scriptSetup;
|
|
677
|
+
if (state && !state.hadScriptSetup) parsed.descriptor.scriptSetup = null;
|
|
678
|
+
else if (state && scriptSetup && state.scriptSetupContent !== void 0) {
|
|
679
|
+
scriptSetup.content = state.scriptSetupContent;
|
|
680
|
+
scriptSetup.loc.source = state.scriptSetupSource ?? state.scriptSetupContent;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
function enhanceSfc(parsed) {
|
|
684
|
+
const descriptor = parsed.descriptor;
|
|
685
|
+
const scriptSetup = descriptor.scriptSetup;
|
|
686
|
+
rawSfcStates.set(parsed, {
|
|
687
|
+
hadScriptSetup: Boolean(scriptSetup),
|
|
688
|
+
scriptSetupContent: scriptSetup?.content,
|
|
689
|
+
scriptSetupSource: scriptSetup?.loc.source
|
|
690
|
+
});
|
|
691
|
+
const wxsModuleNames = collectWxsModuleNames(descriptor.template?.content);
|
|
692
|
+
const scriptSetupLang = resolveScriptSetupLang(scriptSetup?.lang);
|
|
693
|
+
const defineOptionsDeclarations = scriptSetup?.content.includes("defineOptions") ? getDefineOptionsDeclarations(scriptSetup.content, scriptSetupLang) : "";
|
|
694
|
+
if (!wxsModuleNames.length && !defineOptionsDeclarations) return;
|
|
695
|
+
if (scriptSetup) {
|
|
696
|
+
scriptSetup.content = appendScriptSetupDeclarations(appendWxsDeclarations(scriptSetup.content, wxsModuleNames), defineOptionsDeclarations);
|
|
697
|
+
syncScriptBlockSource(scriptSetup);
|
|
698
|
+
} else descriptor.scriptSetup = createSyntheticScriptSetup(wxsModuleNames);
|
|
417
699
|
}
|
|
418
700
|
return {
|
|
419
701
|
name,
|
|
420
702
|
version: PLUGIN_VERSION,
|
|
421
703
|
order: -1,
|
|
422
704
|
parseSFC2(fileName, languageId, content) {
|
|
423
|
-
if (languageId !== "vue") return;
|
|
424
|
-
const parsed =
|
|
425
|
-
|
|
426
|
-
const descriptor = parsed.descriptor;
|
|
427
|
-
const wxsModuleNames = collectWxsModuleNames(descriptor.template?.content);
|
|
428
|
-
const scriptSetup = descriptor.scriptSetup;
|
|
429
|
-
const scriptSetupLang = resolveScriptSetupLang(scriptSetup?.lang);
|
|
430
|
-
const defineOptionsDeclarations = scriptSetup?.content && tsModule ? createDefineOptionsTemplateDeclarations(scriptSetup.content, tsModule, scriptSetupLang) : "";
|
|
431
|
-
if (!wxsModuleNames.length && !defineOptionsDeclarations) return parsed;
|
|
432
|
-
if (descriptor.scriptSetup) {
|
|
433
|
-
descriptor.scriptSetup.content = appendScriptSetupDeclarations(appendWxsDeclarations(descriptor.scriptSetup.content, wxsModuleNames), defineOptionsDeclarations);
|
|
434
|
-
syncScriptBlockSource(descriptor.scriptSetup);
|
|
435
|
-
} else descriptor.scriptSetup = createSyntheticScriptSetup(wxsModuleNames);
|
|
705
|
+
if (languageId !== "vue" || !compilerDom) return;
|
|
706
|
+
const parsed = parseSfc(compilerDom, content, fileName);
|
|
707
|
+
enhanceSfc(parsed);
|
|
436
708
|
return parsed;
|
|
437
709
|
},
|
|
710
|
+
updateSFC(oldResult, change) {
|
|
711
|
+
restoreRawSfc(oldResult);
|
|
712
|
+
const updated = updateSfc(oldResult, change);
|
|
713
|
+
if (!updated) return;
|
|
714
|
+
enhanceSfc(updated);
|
|
715
|
+
return updated;
|
|
716
|
+
},
|
|
438
717
|
getEmbeddedCodes(_, sfc) {
|
|
439
718
|
return getEmbeddedCodesFromCustomBlocks(sfc);
|
|
440
719
|
},
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type * as CompilerDOM from '@vue/compiler-dom';
|
|
2
|
+
import type { SFCParseResult } from '@vue/language-core';
|
|
3
|
+
export interface SfcTextChange {
|
|
4
|
+
start: number;
|
|
5
|
+
end: number;
|
|
6
|
+
newText: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function parseSfc(compilerDom: typeof CompilerDOM, source: string, filename: string): SFCParseResult;
|
|
9
|
+
export declare function updateSfc(sfc: SFCParseResult, change: SfcTextChange): SFCParseResult | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weapp-vite/volar",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Volar plugin for weapp-vite - Provides IntelliSense and type checking for WeChat mini-program config blocks",
|
|
5
5
|
"author": "ice breaker <1324318532@qq.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"@weapp-core/schematics": "6.0.4"
|
|
44
44
|
},
|
|
45
45
|
"scripts": {
|
|
46
|
+
"benchmark": "node --import tsx benchmark/sfc.ts",
|
|
46
47
|
"dev": "tsdown -w --sourcemap",
|
|
47
48
|
"build": "tsdown --no-dts && tsc -p tsconfig.build.json",
|
|
48
49
|
"test": "vitest run",
|