compmark-vue 0.3.3 → 0.4.0

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/dist/index.d.mts CHANGED
@@ -34,6 +34,24 @@ interface ComposableDoc {
34
34
  source?: string;
35
35
  variables: ComposableVariable[];
36
36
  }
37
+ interface RefDoc {
38
+ name: string;
39
+ type: string;
40
+ description: string;
41
+ deprecated?: string | boolean;
42
+ since?: string;
43
+ example?: string;
44
+ see?: string;
45
+ }
46
+ interface ComputedDoc {
47
+ name: string;
48
+ type: string;
49
+ description: string;
50
+ deprecated?: string | boolean;
51
+ since?: string;
52
+ example?: string;
53
+ see?: string;
54
+ }
37
55
  interface ComponentDoc {
38
56
  name: string;
39
57
  description?: string;
@@ -44,6 +62,8 @@ interface ComponentDoc {
44
62
  slots?: SlotDoc[];
45
63
  exposes?: ExposeDoc[];
46
64
  composables?: ComposableDoc[];
65
+ refs?: RefDoc[];
66
+ computeds?: ComputedDoc[];
47
67
  }
48
68
  type OutputFormat = "md" | "json";
49
69
  interface RunSummary {
@@ -59,6 +79,11 @@ interface RunSummary {
59
79
  error: string;
60
80
  }>;
61
81
  }
82
+ interface DiscoveryResult {
83
+ files: string[];
84
+ ignoredCount: number;
85
+ basePath: string;
86
+ }
62
87
  //#endregion
63
88
  //#region src/parser.d.ts
64
89
  declare function parseSFC(source: string, filename: string, sfcDir?: string): ComponentDoc;
@@ -68,7 +93,7 @@ declare function generateMarkdown(doc: ComponentDoc): string;
68
93
  declare function adjustHeadingLevel(md: string, increment: number): string;
69
94
  //#endregion
70
95
  //#region src/discovery.d.ts
71
- declare function discoverFiles(inputs: string[], ignore?: string[]): Promise<string[]>;
96
+ declare function discoverFiles(inputs: string[], ignore?: string[]): Promise<DiscoveryResult>;
72
97
  //#endregion
73
98
  //#region src/runner.d.ts
74
99
  declare function processFiles(filePaths: string[], options: {
@@ -83,4 +108,4 @@ declare function resolveImportedPropsType(typeName: string, importMap: Map<strin
83
108
  //#region src/index.d.ts
84
109
  declare function parseComponent(filePath: string): ComponentDoc;
85
110
  //#endregion
86
- export { type ComponentDoc, type ComposableDoc, type ComposableVariable, type EmitDoc, type ExposeDoc, type OutputFormat, type PropDoc, type RunSummary, type SlotDoc, adjustHeadingLevel, discoverFiles, generateMarkdown, parseComponent, parseSFC, processFiles, resolveImportedPropsType };
111
+ export { type ComponentDoc, type ComposableDoc, type ComposableVariable, type ComputedDoc, type DiscoveryResult, type EmitDoc, type ExposeDoc, type OutputFormat, type PropDoc, type RefDoc, type RunSummary, type SlotDoc, adjustHeadingLevel, discoverFiles, generateMarkdown, parseComponent, parseSFC, processFiles, resolveImportedPropsType };
package/dist/index.mjs CHANGED
@@ -169,15 +169,15 @@ function inferType(node) {
169
169
  if (typeParams?.params?.length > 0) return `Ref<${resolveTypeAnnotation(typeParams.params[0])}>`;
170
170
  const arg = node.arguments[0];
171
171
  if (!arg) return "Ref<unknown>";
172
- return `Ref<${inferLiteralType(arg)}>`;
172
+ return `Ref<${inferLiteralType$1(arg)}>`;
173
173
  }
174
174
  if (node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "computed") return "ComputedRef";
175
175
  if (node.type === "CallExpression" && node.callee.type === "Identifier" && node.callee.name === "reactive") return "Object";
176
176
  if (node.type === "ArrowFunctionExpression" || node.type === "FunctionExpression") return inferFunctionSignature(node);
177
177
  if (node.type === "CallExpression" && node.callee.type === "Identifier" && /^use[A-Z]/.test(node.callee.name)) return "unknown";
178
- return inferLiteralType(node);
178
+ return inferLiteralType$1(node);
179
179
  }
180
- function inferLiteralType(node) {
180
+ function inferLiteralType$1(node) {
181
181
  switch (node.type) {
182
182
  case "NumericLiteral": return "number";
183
183
  case "StringLiteral": return "string";
@@ -390,12 +390,17 @@ function parseSFC(source, filename, sfcDir) {
390
390
  else if (callee === "defineExpose" && args[0]?.type === "ObjectExpression") doc.exposes = extractExposes(args[0], scriptSource);
391
391
  }
392
392
  doc.composables = extractComposables(setupAst, importMap, sfcDir);
393
+ const { refs, computeds } = extractRefsAndComputeds(setupAst, scriptSource);
394
+ if (refs.length > 0) doc.refs = refs;
395
+ if (computeds.length > 0) doc.computeds = computeds;
393
396
  }
394
397
  const scriptAst = compiled.scriptAst;
395
398
  if (scriptAst && doc.props.length === 0 && doc.emits.length === 0) {
396
399
  const optionsDoc = extractOptionsAPI(scriptAst, compiled.content);
397
400
  doc.props = optionsDoc.props;
398
401
  doc.emits = optionsDoc.emits;
402
+ if (optionsDoc.refs.length > 0) doc.refs = optionsDoc.refs;
403
+ if (optionsDoc.computeds.length > 0) doc.computeds = optionsDoc.computeds;
399
404
  }
400
405
  return doc;
401
406
  }
@@ -404,7 +409,8 @@ function extractComponentJSDoc(ast) {
404
409
  description: "",
405
410
  internal: false
406
411
  };
407
- const comments = ast[0].leadingComments ?? [];
412
+ const first = ast[0];
413
+ const comments = first.leadingComments ?? [];
408
414
  if (comments.length === 0) return {
409
415
  description: "",
410
416
  internal: false
@@ -419,6 +425,13 @@ function extractComponentJSDoc(ast) {
419
425
  description: result.description,
420
426
  internal: true
421
427
  };
428
+ const isImport = first.type === "ImportDeclaration";
429
+ const isDefineCall = first.type === "ExpressionStatement" && first.expression.type === "CallExpression" && first.expression.callee.type === "Identifier" && first.expression.callee.name.startsWith("define");
430
+ const isDefineVar = first.type === "VariableDeclaration" && first.declarations.some((d) => d.init?.type === "CallExpression" && d.init.callee?.type === "Identifier" && (d.init.callee.name.startsWith("define") || d.init.callee.name === "withDefaults"));
431
+ if (firstComment.value.includes("@component") || isImport || isDefineCall || isDefineVar) return {
432
+ description: result.description,
433
+ internal: false
434
+ };
422
435
  return {
423
436
  description: "",
424
437
  internal: false
@@ -758,6 +771,99 @@ function extractComposables(ast, importMap, sfcDir) {
758
771
  }
759
772
  return composables;
760
773
  }
774
+ const REF_CALLEES = new Set([
775
+ "ref",
776
+ "shallowRef",
777
+ "reactive",
778
+ "shallowReactive"
779
+ ]);
780
+ const COMPUTED_CALLEES = new Set(["computed"]);
781
+ const SKIP_CALLEES = new Set([
782
+ "defineProps",
783
+ "defineEmits",
784
+ "defineSlots",
785
+ "defineExpose",
786
+ "withDefaults"
787
+ ]);
788
+ function inferLiteralType(node) {
789
+ switch (node.type) {
790
+ case "StringLiteral": return "string";
791
+ case "NumericLiteral": return "number";
792
+ case "BooleanLiteral": return "boolean";
793
+ case "ArrayExpression": return "Array";
794
+ case "ObjectExpression": return "Object";
795
+ case "NullLiteral": return "null";
796
+ default: return null;
797
+ }
798
+ }
799
+ function extractRefsAndComputeds(ast, _scriptSource) {
800
+ const refs = [];
801
+ const computeds = [];
802
+ for (const stmt of ast) {
803
+ if (stmt.type !== "VariableDeclaration") continue;
804
+ const comments = stmt.leadingComments ?? [];
805
+ for (const decl of stmt.declarations) {
806
+ if (!decl.init || decl.init.type !== "CallExpression") continue;
807
+ if (decl.init.callee.type !== "Identifier") continue;
808
+ const calleeName = decl.init.callee.name;
809
+ if (/^use[A-Z]/.test(calleeName)) continue;
810
+ if (SKIP_CALLEES.has(calleeName)) continue;
811
+ const isRef = REF_CALLEES.has(calleeName);
812
+ const isComputed = COMPUTED_CALLEES.has(calleeName);
813
+ if (!isRef && !isComputed) continue;
814
+ const id = decl.id;
815
+ if (!id || id.type !== "Identifier") continue;
816
+ const varName = id.name;
817
+ const jsdoc = parseJSDocTags(comments);
818
+ const callExpr = decl.init;
819
+ const typeParams = callExpr.typeParameters;
820
+ const args = callExpr.arguments;
821
+ let type;
822
+ if (id.typeAnnotation?.typeAnnotation) type = resolveTypeString(id.typeAnnotation.typeAnnotation);
823
+ else if (typeParams?.params?.[0]) {
824
+ const genericType = resolveTypeString(typeParams.params[0]);
825
+ if (calleeName === "reactive" || calleeName === "shallowReactive") type = genericType;
826
+ else if (calleeName === "shallowRef") type = `ShallowRef<${genericType}>`;
827
+ else if (calleeName === "computed") type = `ComputedRef<${genericType}>`;
828
+ else type = `Ref<${genericType}>`;
829
+ } else if (isRef && args[0]) {
830
+ const literalType = inferLiteralType(args[0]);
831
+ if (literalType) if (calleeName === "reactive" || calleeName === "shallowReactive") type = literalType;
832
+ else if (calleeName === "shallowRef") type = `ShallowRef<${literalType}>`;
833
+ else type = `Ref<${literalType}>`;
834
+ else if (calleeName === "reactive") type = "Object";
835
+ else if (calleeName === "shallowReactive") type = "Object";
836
+ else if (calleeName === "shallowRef") type = "ShallowRef";
837
+ else type = "Ref";
838
+ } else if (isComputed && args[0]) {
839
+ let returnType = null;
840
+ const getter = args[0];
841
+ if ((getter.type === "ArrowFunctionExpression" || getter.type === "FunctionExpression") && getter.returnType?.type === "TSTypeAnnotation") returnType = resolveTypeString(getter.returnType.typeAnnotation);
842
+ if (returnType) type = `ComputedRef<${returnType}>`;
843
+ else type = "ComputedRef";
844
+ } else if (calleeName === "reactive") type = "Object";
845
+ else if (calleeName === "shallowReactive") type = "Object";
846
+ else if (calleeName === "shallowRef") type = "ShallowRef";
847
+ else if (calleeName === "computed") type = "ComputedRef";
848
+ else type = "Ref";
849
+ const docEntry = {
850
+ name: varName,
851
+ type,
852
+ description: jsdoc.description,
853
+ ...jsdoc.deprecated !== void 0 && { deprecated: jsdoc.deprecated },
854
+ ...jsdoc.since && { since: jsdoc.since },
855
+ ...jsdoc.example && { example: jsdoc.example },
856
+ ...jsdoc.see && { see: jsdoc.see }
857
+ };
858
+ if (isRef) refs.push(docEntry);
859
+ else computeds.push(docEntry);
860
+ }
861
+ }
862
+ return {
863
+ refs,
864
+ computeds
865
+ };
866
+ }
761
867
  function extractTemplateSlots(templateAst) {
762
868
  const slots = [];
763
869
  walkTemplate(templateAst.children ?? [], slots);
@@ -787,31 +893,110 @@ function walkTemplate(children, slots) {
787
893
  function extractOptionsAPI(ast, source) {
788
894
  let props = [];
789
895
  let emits = [];
896
+ let refs = [];
897
+ let computeds = [];
790
898
  for (const stmt of ast) {
791
899
  if (stmt.type !== "ExportDefaultDeclaration") continue;
792
900
  const decl = stmt.declaration;
793
901
  if (decl.type !== "ObjectExpression") continue;
794
902
  for (const prop of decl.properties) {
795
- if (prop.type !== "ObjectProperty") continue;
903
+ if (prop.type !== "ObjectProperty" && prop.type !== "ObjectMethod") continue;
796
904
  const key = prop.key;
797
905
  const name = key.type === "Identifier" ? key.name : "";
798
906
  if (name === "props") {
907
+ if (prop.type !== "ObjectProperty") continue;
799
908
  if (prop.value.type === "ObjectExpression") props = extractProps(prop.value, source);
800
909
  else if (prop.value.type === "ArrayExpression") props = extractArrayProps(prop.value);
801
910
  } else if (name === "emits") {
911
+ if (prop.type !== "ObjectProperty") continue;
802
912
  if (prop.value.type === "ArrayExpression") {
803
913
  const comments = stmt.leadingComments ?? [];
804
914
  emits = extractEmits(prop.value, comments);
805
915
  } else if (prop.value.type === "ObjectExpression") emits = extractObjectEmits(prop.value);
916
+ } else if (name === "data") refs = extractOptionsData(prop);
917
+ else if (name === "computed") {
918
+ if (prop.type !== "ObjectProperty") continue;
919
+ if (prop.value.type === "ObjectExpression") computeds = extractOptionsComputed(prop.value);
806
920
  }
807
921
  }
808
922
  break;
809
923
  }
810
924
  return {
811
925
  props,
812
- emits
926
+ emits,
927
+ refs,
928
+ computeds
813
929
  };
814
930
  }
931
+ function extractOptionsData(prop) {
932
+ const refs = [];
933
+ let body = null;
934
+ if (prop.type === "ObjectMethod") body = prop.body;
935
+ else if (prop.type === "ObjectProperty") {
936
+ const val = prop.value;
937
+ if (val.type === "ArrowFunctionExpression" || val.type === "FunctionExpression") body = val.body;
938
+ }
939
+ if (!body) return refs;
940
+ let returnArg = null;
941
+ if (body.type === "ObjectExpression") returnArg = body;
942
+ else if (body.type === "BlockStatement") {
943
+ for (const s of body.body) if (s.type === "ReturnStatement" && s.argument?.type === "ObjectExpression") {
944
+ returnArg = s.argument;
945
+ break;
946
+ }
947
+ }
948
+ if (!returnArg || returnArg.type !== "ObjectExpression") return refs;
949
+ for (const p of returnArg.properties) {
950
+ if (p.type !== "ObjectProperty") continue;
951
+ const name = p.key.type === "Identifier" ? p.key.name : p.key.type === "StringLiteral" ? p.key.value : "";
952
+ if (!name) continue;
953
+ const jsdoc = parseJSDocTags(p.leadingComments ?? []);
954
+ const type = inferLiteralType(p.value) ?? "unknown";
955
+ refs.push({
956
+ name,
957
+ type,
958
+ description: jsdoc.description,
959
+ ...jsdoc.deprecated !== void 0 && { deprecated: jsdoc.deprecated },
960
+ ...jsdoc.since && { since: jsdoc.since },
961
+ ...jsdoc.example && { example: jsdoc.example },
962
+ ...jsdoc.see && { see: jsdoc.see }
963
+ });
964
+ }
965
+ return refs;
966
+ }
967
+ function extractOptionsComputed(obj) {
968
+ const computeds = [];
969
+ for (const prop of obj.properties) {
970
+ if (prop.type !== "ObjectProperty" && prop.type !== "ObjectMethod") continue;
971
+ const name = prop.key.type === "Identifier" ? prop.key.name : prop.key.type === "StringLiteral" ? prop.key.value : "";
972
+ if (!name) continue;
973
+ const jsdoc = parseJSDocTags(prop.leadingComments ?? []);
974
+ let type = "unknown";
975
+ if (prop.type === "ObjectMethod") {
976
+ if (prop.returnType?.type === "TSTypeAnnotation") type = resolveTypeString(prop.returnType.typeAnnotation);
977
+ } else if (prop.type === "ObjectProperty") {
978
+ const val = prop.value;
979
+ if (val.type === "ObjectExpression") {
980
+ for (const member of val.properties) if (member.type === "ObjectMethod" && member.key?.type === "Identifier" && member.key.name === "get") {
981
+ if (member.returnType?.type === "TSTypeAnnotation") type = resolveTypeString(member.returnType.typeAnnotation);
982
+ break;
983
+ }
984
+ } else if (val.type === "ArrowFunctionExpression" || val.type === "FunctionExpression") {
985
+ if (val.returnType?.type === "TSTypeAnnotation") type = resolveTypeString(val.returnType.typeAnnotation);
986
+ }
987
+ }
988
+ computeds.push({
989
+ name,
990
+ type,
991
+ description: jsdoc.description,
992
+ ...jsdoc.deprecated !== void 0 && { deprecated: jsdoc.deprecated },
993
+ ...jsdoc.since && { since: jsdoc.since },
994
+ ...jsdoc.example && { example: jsdoc.example },
995
+ ...jsdoc.see && { see: jsdoc.see }
996
+ });
997
+ }
998
+ return computeds;
999
+ }
815
1000
  function extractArrayProps(arr) {
816
1001
  const props = [];
817
1002
  for (const el of arr.elements) if (el?.type === "StringLiteral") props.push({
@@ -862,15 +1047,53 @@ function generateMarkdown(doc) {
862
1047
  const sections = [`# ${doc.name}`];
863
1048
  if (doc.description) sections.push("", doc.description);
864
1049
  if (doc.scriptSetup) sections.push("", "**Note:** Uses `<script setup>` syntax.");
1050
+ const hasRefs = (doc.refs?.length ?? 0) > 0;
1051
+ const hasComputeds = (doc.computeds?.length ?? 0) > 0;
865
1052
  const hasProps = doc.props.length > 0;
866
1053
  const hasEmits = doc.emits.length > 0;
867
1054
  const hasSlots = (doc.slots?.length ?? 0) > 0;
868
1055
  const hasExposes = (doc.exposes?.length ?? 0) > 0;
869
1056
  const hasComposables = (doc.composables?.length ?? 0) > 0;
870
- if (!hasProps && !hasEmits && !hasSlots && !hasExposes && !hasComposables) {
1057
+ if (!hasProps && !hasEmits && !hasSlots && !hasExposes && !hasComposables && !hasRefs && !hasComputeds) {
871
1058
  sections.push("", "No documentable API found.");
872
1059
  return sections.join("\n") + "\n";
873
1060
  }
1061
+ if (hasRefs) {
1062
+ sections.push("", "## Refs", "");
1063
+ sections.push("| Name | Type | Description |");
1064
+ sections.push("| --- | --- | --- |");
1065
+ const examples = [];
1066
+ for (const r of doc.refs) {
1067
+ let desc = r.description || "-";
1068
+ if (r.deprecated) desc += typeof r.deprecated === "string" && r.deprecated ? ` **Deprecated**: ${r.deprecated}` : " **Deprecated**";
1069
+ if (r.since) desc += ` *(since ${r.since})*`;
1070
+ if (r.see) desc += ` See: ${r.see}`;
1071
+ sections.push(`| ${esc(r.name)} | ${escHtml(esc(r.type))} | ${esc(desc)} |`);
1072
+ if (r.example) examples.push({
1073
+ name: r.name,
1074
+ example: r.example
1075
+ });
1076
+ }
1077
+ for (const { name, example } of examples) sections.push("", `**\`${name}\` example:**`, "", "```", example, "```");
1078
+ }
1079
+ if (hasComputeds) {
1080
+ sections.push("", "## Computed", "");
1081
+ sections.push("| Name | Type | Description |");
1082
+ sections.push("| --- | --- | --- |");
1083
+ const examples = [];
1084
+ for (const c of doc.computeds) {
1085
+ let desc = c.description || "-";
1086
+ if (c.deprecated) desc += typeof c.deprecated === "string" && c.deprecated ? ` **Deprecated**: ${c.deprecated}` : " **Deprecated**";
1087
+ if (c.since) desc += ` *(since ${c.since})*`;
1088
+ if (c.see) desc += ` See: ${c.see}`;
1089
+ sections.push(`| ${esc(c.name)} | ${escHtml(esc(c.type))} | ${esc(desc)} |`);
1090
+ if (c.example) examples.push({
1091
+ name: c.name,
1092
+ example: c.example
1093
+ });
1094
+ }
1095
+ for (const { name, example } of examples) sections.push("", `**\`${name}\` example:**`, "", "```", example, "```");
1096
+ }
874
1097
  if (hasProps) {
875
1098
  sections.push("", "## Props", "");
876
1099
  sections.push("| Name | Type | Required | Default | Description |");
@@ -962,17 +1185,44 @@ async function discoverFiles(inputs, ignore) {
962
1185
  const baseIgnore = ["**/node_modules/**"];
963
1186
  const userIgnore = (ignore ?? []).map(normalizeIgnorePattern);
964
1187
  const allIgnore = [...baseIgnore, ...userIgnore];
1188
+ const hasUserIgnore = userIgnore.length > 0;
965
1189
  const found = /* @__PURE__ */ new Set();
1190
+ let ignoredCount = 0;
966
1191
  for (const input of inputs) {
967
1192
  const resolved = resolve(input);
968
1193
  if (input.endsWith(".vue") && existsSync(resolved)) found.add(resolved);
969
- else if (isDirectory(resolved)) {
1194
+ else if (isDirectory(resolved)) if (hasUserIgnore) {
1195
+ const allFiles = await glob("**/*.vue", {
1196
+ cwd: resolved,
1197
+ absolute: true,
1198
+ ignore: baseIgnore
1199
+ });
1200
+ const filteredFiles = await glob("**/*.vue", {
1201
+ cwd: resolved,
1202
+ absolute: true,
1203
+ ignore: allIgnore
1204
+ });
1205
+ ignoredCount += allFiles.length - filteredFiles.length;
1206
+ for (const f of filteredFiles) found.add(f);
1207
+ } else {
970
1208
  const files = await glob("**/*.vue", {
971
1209
  cwd: resolved,
972
1210
  absolute: true,
973
1211
  ignore: allIgnore
974
1212
  });
975
1213
  for (const f of files) found.add(f);
1214
+ }
1215
+ else if (hasUserIgnore) {
1216
+ const allFiles = await glob(input, {
1217
+ absolute: true,
1218
+ ignore: baseIgnore
1219
+ });
1220
+ const filteredFiles = await glob(input, {
1221
+ absolute: true,
1222
+ ignore: allIgnore
1223
+ });
1224
+ ignoredCount += allFiles.length - filteredFiles.length;
1225
+ for (const f of filteredFiles) found.add(f);
976
1226
  } else {
977
1227
  const files = await glob(input, {
978
1228
  absolute: true,
@@ -981,7 +1231,25 @@ async function discoverFiles(inputs, ignore) {
981
1231
  for (const f of files) found.add(f);
982
1232
  }
983
1233
  }
984
- return [...found].sort();
1234
+ const files = [...found].sort();
1235
+ const basePath = computeBasePath(files);
1236
+ return {
1237
+ files,
1238
+ ignoredCount,
1239
+ basePath
1240
+ };
1241
+ }
1242
+ function computeBasePath(files) {
1243
+ if (files.length === 0) return ".";
1244
+ if (files.length === 1) return dirname(files[0]);
1245
+ const parts = files.map((f) => dirname(f).split("/"));
1246
+ const common = [];
1247
+ for (let i = 0; i < parts[0].length; i++) {
1248
+ const segment = parts[0][i];
1249
+ if (parts.every((p) => p[i] === segment)) common.push(segment);
1250
+ else break;
1251
+ }
1252
+ return common.join("/") || "/";
985
1253
  }
986
1254
  function isDirectory(path) {
987
1255
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "compmark-vue",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "Auto-generate Markdown documentation from Vue 3 SFCs",
5
5
  "keywords": [
6
6
  "compmark",