@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 CHANGED
@@ -1,58 +1,72 @@
1
1
  # @weapp-vite/volar
2
2
 
3
- > 为 weapp-vite 提供 VSCode 智能提示和类型检查的 Volar 插件
3
+ ## 1. 简介
4
4
 
5
- ## 安装
5
+ `@weapp-vite/volar` 是 `weapp-vite` 的 Volar 语言服务插件,用来给小程序 Vue SFC 中的自定义块和相关宏提供更准确的类型、补全和诊断能力。
6
6
 
7
- 此包已作为 `weapp-vite` 的依赖自动安装,**无需单独安装**。
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
- npm install -D @weapp-vite/volar
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
- 详细使用文档请参考:[weapp-vite Volar 文档](../weapp-vite/docs/volar.md)
28
+ ```json
29
+ {
30
+ "vueCompilerOptions": {
31
+ "plugins": ["weapp-vite/volar"]
32
+ }
33
+ }
34
+ ```
24
35
 
25
- ## 功能特性
36
+ > **注意**:更完整的编辑器接入方式,请优先参考 `weapp-vite` 主包文档中的 Volar 章节。
26
37
 
27
- - ✅ **配置文件智能提示** - 为 `<json>` 代码块提供完整的类型检查和自动补全
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
- ```bash
36
- # 安装依赖
37
- pnpm install
47
+ ## 5. 适用场景
38
48
 
39
- # 开发模式
40
- pnpm dev
49
+ - 在 `.vue` 文件里直接编写小程序页面 / 组件配置
50
+ - 使用 `definePageJson`、`defineComponentJson` 等宏
51
+ - 需要让 VS Code / Volar 正确理解 weapp-vite 的自定义块与扩展语义
41
52
 
42
- # 构建
43
- pnpm build
53
+ ## 6. 本地开发
44
54
 
45
- # 测试
46
- pnpm test
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
- - [weapp-vite 文档](../weapp-vite)
52
- - [weapp-vite Volar 使用指南](../weapp-vite/docs/volar.md)
53
- - [Vue 3 文档](https://vuejs.org/)
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
- ## License
68
+ ## 7. 相关链接
57
69
 
58
- MIT
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)
@@ -1,4 +1,5 @@
1
1
  export declare const BLOCK_TYPE = "json";
2
+ export declare const DEFINE_OPTIONS_CACHE_MAX = 128;
2
3
  export declare const JS_LANG = "js";
3
4
  export declare const JSONC_LANG = "jsonc";
4
5
  export declare const JSON_LANG = "json";
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(code, tsModule, lang) {
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 findReturnedObjectLiteral(node, tsModule) {
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
- const expression = unwrapParenthesizedExpression(statement.expression, tsModule);
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 collectObjectLiteralKeys(node, tsModule) {
350
- const keys = [];
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) && !tsModule.isMethodDeclaration(property) && !tsModule.isShorthandPropertyAssignment(property) && !tsModule.isGetAccessorDeclaration(property)) continue;
353
- const key = getPropertyNameText(property.name, tsModule);
354
- if (key && isIdentifierName(key)) keys.push(key);
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 keys;
534
+ return "any";
357
535
  }
358
- function collectDefineOptionsTemplateBindings(code, tsModule, lang) {
359
- const scriptKind = lang === "ts" ? 3 : 1;
360
- const sourceFile = tsModule.createSourceFile(`script-setup.${lang}`, code, 99, true, scriptKind);
361
- const valueBindings = /* @__PURE__ */ new Set();
362
- const functionBindings = /* @__PURE__ */ new Set();
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 name of collectObjectLiteralKeys(methodsObject, tsModule)) functionBindings.add(name);
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 name of collectObjectLiteralKeys(objectValue, tsModule)) valueBindings.add(name);
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.isArrowFunction(initializer) || tsModule.isFunctionExpression(initializer)) dataObject = findReturnedObjectLiteral(initializer.body, 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 topLevelBindings = collectTopLevelBindingNames(code, tsModule, lang);
400
- const bindings = collectDefineOptionsTemplateBindings(code, tsModule, lang);
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}: any = null as any`);
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}: (...args: any[]) => any = null as any`);
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
- let tsModule = ctx?.modules?.typescript;
437
- if (!tsModule) try {
438
- tsModule = require$1("typescript");
439
- } catch {
440
- tsModule = void 0;
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 = parseVueSfc(content, fileName);
449
- if (!parsed) return;
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(code, tsModule, lang) {
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 findReturnedObjectLiteral(node, tsModule) {
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
- const expression = unwrapParenthesizedExpression(statement.expression, tsModule);
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 collectObjectLiteralKeys(node, tsModule) {
326
- const keys = [];
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) && !tsModule.isMethodDeclaration(property) && !tsModule.isShorthandPropertyAssignment(property) && !tsModule.isGetAccessorDeclaration(property)) continue;
329
- const key = getPropertyNameText(property.name, tsModule);
330
- if (key && isIdentifierName(key)) keys.push(key);
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 keys;
510
+ return "any";
333
511
  }
334
- function collectDefineOptionsTemplateBindings(code, tsModule, lang) {
335
- const scriptKind = lang === "ts" ? 3 : 1;
336
- const sourceFile = tsModule.createSourceFile(`script-setup.${lang}`, code, 99, true, scriptKind);
337
- const valueBindings = /* @__PURE__ */ new Set();
338
- const functionBindings = /* @__PURE__ */ new Set();
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 name of collectObjectLiteralKeys(methodsObject, tsModule)) functionBindings.add(name);
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 name of collectObjectLiteralKeys(objectValue, tsModule)) valueBindings.add(name);
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.isArrowFunction(initializer) || tsModule.isFunctionExpression(initializer)) dataObject = findReturnedObjectLiteral(initializer.body, 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 topLevelBindings = collectTopLevelBindingNames(code, tsModule, lang);
376
- const bindings = collectDefineOptionsTemplateBindings(code, tsModule, lang);
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}: any = null as any`);
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}: (...args: any[]) => any = null as any`);
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
- let tsModule = ctx?.modules?.typescript;
413
- if (!tsModule) try {
414
- tsModule = require("typescript");
415
- } catch {
416
- tsModule = void 0;
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 = parseVueSfc(content, fileName);
425
- if (!parsed) return;
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.0.8",
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",