@openpkg-ts/extract 0.15.0 → 0.16.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/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-2v40ecks.js";
5
5
 
6
6
  // src/cli/spec.ts
7
7
  import * as fs from "node:fs";
@@ -1609,14 +1609,26 @@ async function extract(options) {
1609
1609
  }
1610
1610
  const meta = await getPackageMeta(entryFile, baseDir);
1611
1611
  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
- });
1612
+ const forgottenExports = collectForgottenExports(exports, types, program, sourceFile);
1613
+ for (const forgotten of forgottenExports) {
1614
+ const refSummary = forgotten.referencedBy.slice(0, 3).map((r) => `${r.exportName} (${r.location})`).join(", ");
1615
+ const moreRefs = forgotten.referencedBy.length > 3 ? ` +${forgotten.referencedBy.length - 3} more` : "";
1616
+ if (forgotten.isExternal) {
1617
+ diagnostics.push({
1618
+ message: `External type '${forgotten.name}' referenced by: ${refSummary}${moreRefs}`,
1619
+ severity: "info",
1620
+ code: "EXTERNAL_TYPE_REF",
1621
+ suggestion: forgotten.definedIn ? `Type is from: ${forgotten.definedIn}` : "Type is from an external package"
1622
+ });
1623
+ } else {
1624
+ diagnostics.push({
1625
+ message: `Forgotten export: '${forgotten.name}' referenced by: ${refSummary}${moreRefs}`,
1626
+ severity: "warning",
1627
+ code: "FORGOTTEN_EXPORT",
1628
+ suggestion: forgotten.fix ?? `Export this type from your public API`,
1629
+ location: forgotten.definedIn ? { file: forgotten.definedIn } : undefined
1630
+ });
1631
+ }
1620
1632
  }
1621
1633
  const externalTypes = types.filter((t) => t.kind === "external");
1622
1634
  if (externalTypes.length > 0) {
@@ -1637,33 +1649,134 @@ async function extract(options) {
1637
1649
  timestamp: new Date().toISOString()
1638
1650
  }
1639
1651
  };
1640
- return { spec, diagnostics };
1652
+ const internalForgotten = forgottenExports.filter((f) => !f.isExternal);
1653
+ return {
1654
+ spec,
1655
+ diagnostics,
1656
+ ...internalForgotten.length > 0 ? { forgottenExports: internalForgotten } : {}
1657
+ };
1641
1658
  }
1642
- function collectAllRefs(obj, refs) {
1659
+ function collectAllRefsWithContext(obj, refs, state) {
1643
1660
  if (obj === null || obj === undefined)
1644
1661
  return;
1645
1662
  if (Array.isArray(obj)) {
1646
- for (const item of obj) {
1647
- collectAllRefs(item, refs);
1663
+ for (let i = 0;i < obj.length; i++) {
1664
+ collectAllRefsWithContext(obj[i], refs, {
1665
+ ...state,
1666
+ path: [...state.path, `[${i}]`]
1667
+ });
1648
1668
  }
1649
1669
  return;
1650
1670
  }
1651
1671
  if (typeof obj === "object") {
1652
1672
  const record = obj;
1653
1673
  if (typeof record.$ref === "string" && record.$ref.startsWith("#/types/")) {
1654
- refs.add(record.$ref.slice("#/types/".length));
1674
+ const typeName = record.$ref.slice("#/types/".length);
1675
+ const existing = refs.get(typeName) ?? [];
1676
+ existing.push({
1677
+ typeName,
1678
+ exportName: state.exportName,
1679
+ location: state.location,
1680
+ path: state.path.join(".") || undefined
1681
+ });
1682
+ refs.set(typeName, existing);
1683
+ }
1684
+ for (const [key, value] of Object.entries(record)) {
1685
+ let newLocation = state.location;
1686
+ if (key === "returnType" || key === "returns")
1687
+ newLocation = "return";
1688
+ else if (key === "parameters" || key === "params")
1689
+ newLocation = "parameter";
1690
+ else if (key === "properties" || key === "members")
1691
+ newLocation = "property";
1692
+ else if (key === "extends" || key === "implements")
1693
+ newLocation = "extends";
1694
+ else if (key === "typeParameters" || key === "typeParams")
1695
+ newLocation = "type-parameter";
1696
+ collectAllRefsWithContext(value, refs, {
1697
+ ...state,
1698
+ location: newLocation,
1699
+ path: [...state.path, key]
1700
+ });
1655
1701
  }
1656
- for (const value of Object.values(record)) {
1657
- collectAllRefs(value, refs);
1702
+ }
1703
+ }
1704
+ function findTypeDefinition(typeName, program, sourceFile) {
1705
+ const checker = program.getTypeChecker();
1706
+ const findInNode = (node) => {
1707
+ if ((ts8.isInterfaceDeclaration(node) || ts8.isTypeAliasDeclaration(node) || ts8.isClassDeclaration(node) || ts8.isEnumDeclaration(node)) && node.name?.text === typeName) {
1708
+ const sf = node.getSourceFile();
1709
+ return sf.fileName;
1710
+ }
1711
+ return ts8.forEachChild(node, findInNode);
1712
+ };
1713
+ const entryResult = findInNode(sourceFile);
1714
+ if (entryResult)
1715
+ return entryResult;
1716
+ for (const sf of program.getSourceFiles()) {
1717
+ if (sf.isDeclarationFile && !sf.fileName.includes("node_modules")) {
1718
+ const result = findInNode(sf);
1719
+ if (result)
1720
+ return result;
1658
1721
  }
1659
1722
  }
1723
+ const symbol = checker.resolveName(typeName, sourceFile, ts8.SymbolFlags.Type, false);
1724
+ if (symbol?.declarations?.[0]) {
1725
+ return symbol.declarations[0].getSourceFile().fileName;
1726
+ }
1727
+ return;
1728
+ }
1729
+ function isExternalType2(definedIn) {
1730
+ if (!definedIn)
1731
+ return true;
1732
+ return definedIn.includes("node_modules");
1660
1733
  }
1661
- function collectDanglingRefs(exports, types) {
1734
+ function hasInternalTag(typeName, program, sourceFile) {
1735
+ const checker = program.getTypeChecker();
1736
+ const symbol = checker.resolveName(typeName, sourceFile, ts8.SymbolFlags.Type, false);
1737
+ if (!symbol)
1738
+ return false;
1739
+ const jsTags = symbol.getJsDocTags();
1740
+ return jsTags.some((tag) => tag.name === "internal");
1741
+ }
1742
+ function collectForgottenExports(exports, types, program, sourceFile) {
1662
1743
  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));
1744
+ const referencedTypes = new Map;
1745
+ for (const exp of exports) {
1746
+ collectAllRefsWithContext(exp, referencedTypes, {
1747
+ exportName: exp.id || exp.name,
1748
+ location: "property",
1749
+ path: []
1750
+ });
1751
+ }
1752
+ for (const type of types) {
1753
+ collectAllRefsWithContext(type, referencedTypes, {
1754
+ exportName: type.id,
1755
+ location: "property",
1756
+ path: []
1757
+ });
1758
+ }
1759
+ const forgottenExports = [];
1760
+ for (const [typeName, references] of referencedTypes) {
1761
+ if (definedTypes.has(typeName))
1762
+ continue;
1763
+ if (BUILTIN_TYPES2.has(typeName))
1764
+ continue;
1765
+ if (shouldSkipDanglingRef(typeName))
1766
+ continue;
1767
+ if (hasInternalTag(typeName, program, sourceFile))
1768
+ continue;
1769
+ const definedIn = findTypeDefinition(typeName, program, sourceFile);
1770
+ const isExternal = isExternalType2(definedIn);
1771
+ forgottenExports.push({
1772
+ name: typeName,
1773
+ definedIn,
1774
+ referencedBy: references,
1775
+ isExternal,
1776
+ fix: isExternal ? undefined : `export { ${typeName} } from '${definedIn ?? "./types"}'`
1777
+ });
1778
+ }
1779
+ return forgottenExports;
1667
1780
  }
1668
1781
  function resolveExportTarget(symbol, checker) {
1669
1782
  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-2v40ecks.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.0",
4
4
  "description": "TypeScript export extraction to OpenPkg spec",
5
5
  "keywords": [
6
6
  "openpkg",