@openpkg-ts/extract 0.15.0 → 0.16.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/dist/bin/tspec.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  extract
4
- } from "../shared/chunk-0bt6mcnx.js";
4
+ } from "../shared/chunk-bgnmsw8f.js";
5
5
 
6
6
  // src/cli/spec.ts
7
7
  import * as fs from "node:fs";
@@ -661,12 +661,33 @@ function parseExamplesFromTags(tags) {
661
661
  }
662
662
  return examples;
663
663
  }
664
+ function stripParamSeparator(text) {
665
+ if (!text)
666
+ return;
667
+ const stripped = text.replace(/^-\s*/, "").trim();
668
+ return stripped || undefined;
669
+ }
670
+ function stripTypeParamSeparator(text) {
671
+ if (!text)
672
+ return;
673
+ const match = text.match(/^\w+\s+-\s*(.*)$/s);
674
+ if (match) {
675
+ return match[1].trim() || undefined;
676
+ }
677
+ return text.trim() || undefined;
678
+ }
664
679
  function getJSDocComment(node) {
665
680
  const jsDocTags = ts3.getJSDocTags(node);
666
- const tags = jsDocTags.map((tag) => ({
667
- name: tag.tagName.text,
668
- text: typeof tag.comment === "string" ? tag.comment : ts3.getTextOfJSDocComment(tag.comment) ?? ""
669
- }));
681
+ const tags = jsDocTags.map((tag) => {
682
+ const rawText = typeof tag.comment === "string" ? tag.comment : ts3.getTextOfJSDocComment(tag.comment) ?? "";
683
+ let text = rawText;
684
+ if (tag.tagName.text === "param") {
685
+ text = stripParamSeparator(rawText) ?? "";
686
+ } else if (tag.tagName.text === "typeParam") {
687
+ text = stripTypeParamSeparator(rawText) ?? "";
688
+ }
689
+ return { name: tag.tagName.text, text };
690
+ });
670
691
  const jsDocComments = node.jsDoc;
671
692
  let description;
672
693
  if (jsDocComments && jsDocComments.length > 0) {
@@ -694,7 +715,7 @@ function getParamDescription(propertyName, jsdocTags, inferredAlias) {
694
715
  const isMatch = tagParamName === propertyName || inferredAlias && tagParamName === `${inferredAlias}.${propertyName}` || tagParamName.endsWith(`.${propertyName}`);
695
716
  if (isMatch) {
696
717
  const comment = typeof tag.comment === "string" ? tag.comment : ts3.getTextOfJSDocComment(tag.comment);
697
- return comment?.trim() || undefined;
718
+ return stripParamSeparator(comment);
698
719
  }
699
720
  }
700
721
  return;
@@ -1609,14 +1630,26 @@ async function extract(options) {
1609
1630
  }
1610
1631
  const meta = await getPackageMeta(entryFile, baseDir);
1611
1632
  const types = ctx.typeRegistry.getAll();
1612
- const danglingRefs = collectDanglingRefs(exports, types);
1613
- for (const ref of danglingRefs) {
1614
- diagnostics.push({
1615
- message: `Type '${ref}' is referenced but not defined in types[].`,
1616
- severity: "warning",
1617
- code: "DANGLING_REF",
1618
- suggestion: "The type may be from an external package. Check import paths."
1619
- });
1633
+ const forgottenExports = collectForgottenExports(exports, types, program, sourceFile);
1634
+ for (const forgotten of forgottenExports) {
1635
+ const refSummary = forgotten.referencedBy.slice(0, 3).map((r) => `${r.exportName} (${r.location})`).join(", ");
1636
+ const moreRefs = forgotten.referencedBy.length > 3 ? ` +${forgotten.referencedBy.length - 3} more` : "";
1637
+ if (forgotten.isExternal) {
1638
+ diagnostics.push({
1639
+ message: `External type '${forgotten.name}' referenced by: ${refSummary}${moreRefs}`,
1640
+ severity: "info",
1641
+ code: "EXTERNAL_TYPE_REF",
1642
+ suggestion: forgotten.definedIn ? `Type is from: ${forgotten.definedIn}` : "Type is from an external package"
1643
+ });
1644
+ } else {
1645
+ diagnostics.push({
1646
+ message: `Forgotten export: '${forgotten.name}' referenced by: ${refSummary}${moreRefs}`,
1647
+ severity: "warning",
1648
+ code: "FORGOTTEN_EXPORT",
1649
+ suggestion: forgotten.fix ?? `Export this type from your public API`,
1650
+ location: forgotten.definedIn ? { file: forgotten.definedIn } : undefined
1651
+ });
1652
+ }
1620
1653
  }
1621
1654
  const externalTypes = types.filter((t) => t.kind === "external");
1622
1655
  if (externalTypes.length > 0) {
@@ -1637,33 +1670,134 @@ async function extract(options) {
1637
1670
  timestamp: new Date().toISOString()
1638
1671
  }
1639
1672
  };
1640
- return { spec, diagnostics };
1673
+ const internalForgotten = forgottenExports.filter((f) => !f.isExternal);
1674
+ return {
1675
+ spec,
1676
+ diagnostics,
1677
+ ...internalForgotten.length > 0 ? { forgottenExports: internalForgotten } : {}
1678
+ };
1641
1679
  }
1642
- function collectAllRefs(obj, refs) {
1680
+ function collectAllRefsWithContext(obj, refs, state) {
1643
1681
  if (obj === null || obj === undefined)
1644
1682
  return;
1645
1683
  if (Array.isArray(obj)) {
1646
- for (const item of obj) {
1647
- collectAllRefs(item, refs);
1684
+ for (let i = 0;i < obj.length; i++) {
1685
+ collectAllRefsWithContext(obj[i], refs, {
1686
+ ...state,
1687
+ path: [...state.path, `[${i}]`]
1688
+ });
1648
1689
  }
1649
1690
  return;
1650
1691
  }
1651
1692
  if (typeof obj === "object") {
1652
1693
  const record = obj;
1653
1694
  if (typeof record.$ref === "string" && record.$ref.startsWith("#/types/")) {
1654
- refs.add(record.$ref.slice("#/types/".length));
1695
+ const typeName = record.$ref.slice("#/types/".length);
1696
+ const existing = refs.get(typeName) ?? [];
1697
+ existing.push({
1698
+ typeName,
1699
+ exportName: state.exportName,
1700
+ location: state.location,
1701
+ path: state.path.join(".") || undefined
1702
+ });
1703
+ refs.set(typeName, existing);
1704
+ }
1705
+ for (const [key, value] of Object.entries(record)) {
1706
+ let newLocation = state.location;
1707
+ if (key === "returnType" || key === "returns")
1708
+ newLocation = "return";
1709
+ else if (key === "parameters" || key === "params")
1710
+ newLocation = "parameter";
1711
+ else if (key === "properties" || key === "members")
1712
+ newLocation = "property";
1713
+ else if (key === "extends" || key === "implements")
1714
+ newLocation = "extends";
1715
+ else if (key === "typeParameters" || key === "typeParams")
1716
+ newLocation = "type-parameter";
1717
+ collectAllRefsWithContext(value, refs, {
1718
+ ...state,
1719
+ location: newLocation,
1720
+ path: [...state.path, key]
1721
+ });
1655
1722
  }
1656
- for (const value of Object.values(record)) {
1657
- collectAllRefs(value, refs);
1723
+ }
1724
+ }
1725
+ function findTypeDefinition(typeName, program, sourceFile) {
1726
+ const checker = program.getTypeChecker();
1727
+ const findInNode = (node) => {
1728
+ if ((ts8.isInterfaceDeclaration(node) || ts8.isTypeAliasDeclaration(node) || ts8.isClassDeclaration(node) || ts8.isEnumDeclaration(node)) && node.name?.text === typeName) {
1729
+ const sf = node.getSourceFile();
1730
+ return sf.fileName;
1658
1731
  }
1732
+ return ts8.forEachChild(node, findInNode);
1733
+ };
1734
+ const entryResult = findInNode(sourceFile);
1735
+ if (entryResult)
1736
+ return entryResult;
1737
+ for (const sf of program.getSourceFiles()) {
1738
+ if (sf.isDeclarationFile && !sf.fileName.includes("node_modules")) {
1739
+ const result = findInNode(sf);
1740
+ if (result)
1741
+ return result;
1742
+ }
1743
+ }
1744
+ const symbol = checker.resolveName(typeName, sourceFile, ts8.SymbolFlags.Type, false);
1745
+ if (symbol?.declarations?.[0]) {
1746
+ return symbol.declarations[0].getSourceFile().fileName;
1659
1747
  }
1748
+ return;
1749
+ }
1750
+ function isExternalType2(definedIn) {
1751
+ if (!definedIn)
1752
+ return true;
1753
+ return definedIn.includes("node_modules");
1754
+ }
1755
+ function hasInternalTag(typeName, program, sourceFile) {
1756
+ const checker = program.getTypeChecker();
1757
+ const symbol = checker.resolveName(typeName, sourceFile, ts8.SymbolFlags.Type, false);
1758
+ if (!symbol)
1759
+ return false;
1760
+ const jsTags = symbol.getJsDocTags();
1761
+ return jsTags.some((tag) => tag.name === "internal");
1660
1762
  }
1661
- function collectDanglingRefs(exports, types) {
1763
+ function collectForgottenExports(exports, types, program, sourceFile) {
1662
1764
  const definedTypes = new Set(types.map((t) => t.id));
1663
- const referencedTypes = new Set;
1664
- collectAllRefs(exports, referencedTypes);
1665
- collectAllRefs(types, referencedTypes);
1666
- return Array.from(referencedTypes).filter((ref) => !definedTypes.has(ref) && !BUILTIN_TYPES2.has(ref) && !shouldSkipDanglingRef(ref));
1765
+ const referencedTypes = new Map;
1766
+ for (const exp of exports) {
1767
+ collectAllRefsWithContext(exp, referencedTypes, {
1768
+ exportName: exp.id || exp.name,
1769
+ location: "property",
1770
+ path: []
1771
+ });
1772
+ }
1773
+ for (const type of types) {
1774
+ collectAllRefsWithContext(type, referencedTypes, {
1775
+ exportName: type.id,
1776
+ location: "property",
1777
+ path: []
1778
+ });
1779
+ }
1780
+ const forgottenExports = [];
1781
+ for (const [typeName, references] of referencedTypes) {
1782
+ if (definedTypes.has(typeName))
1783
+ continue;
1784
+ if (BUILTIN_TYPES2.has(typeName))
1785
+ continue;
1786
+ if (shouldSkipDanglingRef(typeName))
1787
+ continue;
1788
+ if (hasInternalTag(typeName, program, sourceFile))
1789
+ continue;
1790
+ const definedIn = findTypeDefinition(typeName, program, sourceFile);
1791
+ const isExternal = isExternalType2(definedIn);
1792
+ forgottenExports.push({
1793
+ name: typeName,
1794
+ definedIn,
1795
+ referencedBy: references,
1796
+ isExternal,
1797
+ fix: isExternal ? undefined : `export { ${typeName} } from '${definedIn ?? "./types"}'`
1798
+ });
1799
+ }
1800
+ return forgottenExports;
1667
1801
  }
1668
1802
  function resolveExportTarget(symbol, checker) {
1669
1803
  let targetSymbol = symbol;
@@ -79,6 +79,7 @@ interface ExtractOptions {
79
79
  interface ExtractResult {
80
80
  spec: OpenPkg;
81
81
  diagnostics: Diagnostic[];
82
+ forgottenExports?: ForgottenExport[];
82
83
  }
83
84
  interface Diagnostic {
84
85
  message: string;
@@ -91,6 +92,21 @@ interface Diagnostic {
91
92
  column?: number;
92
93
  };
93
94
  }
95
+ /** Context tracking for type references in public API */
96
+ interface TypeReference2 {
97
+ typeName: string;
98
+ exportName: string;
99
+ location: "return" | "parameter" | "property" | "extends" | "type-parameter";
100
+ path?: string;
101
+ }
102
+ /** Structured data for forgotten exports */
103
+ interface ForgottenExport {
104
+ name: string;
105
+ definedIn?: string;
106
+ referencedBy: TypeReference2[];
107
+ isExternal: boolean;
108
+ fix?: string;
109
+ }
94
110
  declare function extract(options: ExtractOptions): Promise<ExtractResult>;
95
111
  import ts4 from "typescript";
96
112
  interface ProgramOptions {
@@ -309,4 +325,4 @@ declare function findDiscriminatorProperty(unionTypes: ts12.Type[], checker: ts1
309
325
  import ts13 from "typescript";
310
326
  declare function isExported(node: ts13.Node): boolean;
311
327
  declare function getNodeName(node: ts13.Node): string | undefined;
312
- export { zodAdapter, withDescription, valibotAdapter, typeboxAdapter, serializeVariable, serializeTypeAlias, serializeInterface, serializeFunctionExport, serializeEnum, serializeClass, schemasAreEqual, schemaIsAny, resolveCompiledPath, registerReferencedTypes, registerAdapter, isTypeReference, isSymbolDeprecated, isStandardJSONSchema, isSchemaType, isPureRefSchema, isPrimitiveName, isExported, isBuiltinGeneric, isAnonymous, getSourceLocation, getParamDescription, getNonNullableType, getNodeName, getJSDocComment, findDiscriminatorProperty, findAdapter, extractTypeParameters, extractStandardSchemasFromProject, extractStandardSchemas, extractSchemaType, extractParameters, extract, deduplicateSchemas, createProgram, buildSchema, arktypeAdapter, TypeRegistry, StandardSchemaExtractionResult, StandardSchemaExtractionOutput, StandardJSONSchemaV1, SerializerContext, SchemaExtractionResult, SchemaAdapter, ProgramResult, ProgramOptions, ExtractStandardSchemasOptions, ExtractResult, ExtractOptions, Diagnostic, BUILTIN_TYPE_SCHEMAS };
328
+ export { zodAdapter, withDescription, valibotAdapter, typeboxAdapter, serializeVariable, serializeTypeAlias, serializeInterface, serializeFunctionExport, serializeEnum, serializeClass, schemasAreEqual, schemaIsAny, resolveCompiledPath, registerReferencedTypes, registerAdapter, isTypeReference, isSymbolDeprecated, isStandardJSONSchema, isSchemaType, isPureRefSchema, isPrimitiveName, isExported, isBuiltinGeneric, isAnonymous, getSourceLocation, getParamDescription, getNonNullableType, getNodeName, getJSDocComment, findDiscriminatorProperty, findAdapter, extractTypeParameters, extractStandardSchemasFromProject, extractStandardSchemas, extractSchemaType, extractParameters, extract, deduplicateSchemas, createProgram, buildSchema, arktypeAdapter, TypeRegistry, TypeReference2 as TypeReference, StandardSchemaExtractionResult, StandardSchemaExtractionOutput, StandardJSONSchemaV1, SerializerContext, SchemaExtractionResult, SchemaAdapter, ProgramResult, ProgramOptions, ForgottenExport, ExtractStandardSchemasOptions, ExtractResult, ExtractOptions, Diagnostic, BUILTIN_TYPE_SCHEMAS };
package/dist/src/index.js CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  serializeTypeAlias,
27
27
  serializeVariable,
28
28
  withDescription
29
- } from "../shared/chunk-0bt6mcnx.js";
29
+ } from "../shared/chunk-bgnmsw8f.js";
30
30
  // src/schema/registry.ts
31
31
  function isTypeReference(type) {
32
32
  return !!(type.flags & 524288 && type.objectFlags && type.objectFlags & 4);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpkg-ts/extract",
3
- "version": "0.15.0",
3
+ "version": "0.16.1",
4
4
  "description": "TypeScript export extraction to OpenPkg spec",
5
5
  "keywords": [
6
6
  "openpkg",