@contentful/experience-design-system-extraction 2.34.5-dev-build-4a4b9e84.0 → 2.34.5-dev-build-9dff4380.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.
Files changed (88) hide show
  1. package/dist/package.json +1 -1
  2. package/dist/src/extract/{adapters/astro/extractor.d.ts → astro.d.ts} +1 -1
  3. package/dist/src/extract/{adapters/astro/extractor.js → astro.js} +3 -4
  4. package/dist/src/extract/{adapters/support/file-processing/file-workers.d.ts → file-extraction-workers.d.ts} +13 -1
  5. package/dist/src/extract/{adapters/support/file-processing/file-workers.js → file-extraction-workers.js} +19 -0
  6. package/dist/src/extract/{policies/quality/authorability.d.ts → non-authorable-filter.d.ts} +1 -1
  7. package/dist/src/extract/pipeline.d.ts +7 -0
  8. package/dist/src/extract/{services/component-deduplicator.js → pipeline.js} +137 -13
  9. package/dist/src/extract/{adapters/react/extractor.d.ts → react.d.ts} +1 -1
  10. package/dist/src/extract/{adapters/react/extractor.js → react.js} +353 -9
  11. package/dist/src/extract/scoring.d.ts +12 -0
  12. package/dist/src/extract/{policies/quality/scoring.js → scoring.js} +0 -1
  13. package/dist/src/extract/{policies/quality/source-inspection.d.ts → source-inspection.d.ts} +1 -1
  14. package/dist/src/extract/{policies/quality/source-inspection.js → source-inspection.js} +1 -1
  15. package/dist/src/extract/{adapters/support/resolution/source-location.d.ts → source-line-metadata.d.ts} +1 -1
  16. package/dist/src/extract/{adapters/stencil/extractor.d.ts → stencil.d.ts} +1 -1
  17. package/dist/src/extract/{adapters/stencil/extractor.js → stencil.js} +2 -3
  18. package/dist/src/extract/{evidence/structural-slot-evidence.d.ts → structural-slot-evidence.d.ts} +1 -1
  19. package/dist/src/extract/{evidence/structural-slot-evidence.js → structural-slot-evidence.js} +2 -2
  20. package/dist/src/extract/{adapters/svelte/extractor.d.ts → svelte.d.ts} +1 -2
  21. package/dist/src/extract/{adapters/svelte/extractor.js → svelte.js} +91 -7
  22. package/dist/src/extract/{adapters/support/tsx-shared.d.ts → tsx-shared.d.ts} +1 -1
  23. package/dist/src/extract/{policies/quality/validation.d.ts → validate.d.ts} +1 -1
  24. package/dist/src/extract/{adapters/vue-tsx/extractor.d.ts → vue-tsx.d.ts} +1 -1
  25. package/dist/src/extract/{adapters/vue-tsx/extractor.js → vue-tsx.js} +1 -1
  26. package/dist/src/extract/{adapters/vue/extractor.d.ts → vue.d.ts} +1 -1
  27. package/dist/src/extract/{adapters/vue/extractor.js → vue.js} +3 -4
  28. package/dist/src/extract/{adapters/web-components/extractor.d.ts → web-components.d.ts} +1 -1
  29. package/dist/src/extract/{adapters/web-components/extractor.js → web-components.js} +213 -4
  30. package/dist/src/index.d.ts +21 -25
  31. package/dist/src/index.js +17 -18
  32. package/dist/src/{extract/services/classification-service.d.ts → pre-classify.d.ts} +1 -1
  33. package/dist/src/{extract/model/component.d.ts → types.d.ts} +20 -0
  34. package/package.json +1 -1
  35. package/dist/src/extract/adapters/react/function-resolution.d.ts +0 -4
  36. package/dist/src/extract/adapters/react/function-resolution.js +0 -131
  37. package/dist/src/extract/adapters/react/prop-dataflow.d.ts +0 -14
  38. package/dist/src/extract/adapters/react/prop-dataflow.js +0 -220
  39. package/dist/src/extract/adapters/support/file-processing/project-source-files.d.ts +0 -5
  40. package/dist/src/extract/adapters/support/file-processing/project-source-files.js +0 -13
  41. package/dist/src/extract/adapters/support/file-processing/result-normalizer.d.ts +0 -6
  42. package/dist/src/extract/adapters/support/file-processing/result-normalizer.js +0 -6
  43. package/dist/src/extract/adapters/svelte/ast.d.ts +0 -12
  44. package/dist/src/extract/adapters/svelte/ast.js +0 -1
  45. package/dist/src/extract/adapters/svelte/identity.d.ts +0 -1
  46. package/dist/src/extract/adapters/svelte/identity.js +0 -26
  47. package/dist/src/extract/adapters/svelte/slots.d.ts +0 -7
  48. package/dist/src/extract/adapters/svelte/slots.js +0 -63
  49. package/dist/src/extract/adapters/web-components/properties.d.ts +0 -9
  50. package/dist/src/extract/adapters/web-components/properties.js +0 -212
  51. package/dist/src/extract/controller/extract-controller.d.ts +0 -10
  52. package/dist/src/extract/controller/extract-controller.js +0 -56
  53. package/dist/src/extract/model/contract.d.ts +0 -16
  54. package/dist/src/extract/model/contract.js +0 -1
  55. package/dist/src/extract/model/options.d.ts +0 -15
  56. package/dist/src/extract/model/options.js +0 -1
  57. package/dist/src/extract/model/scoring.d.ts +0 -9
  58. package/dist/src/extract/model/scoring.js +0 -1
  59. package/dist/src/extract/policies/quality/scoring.d.ts +0 -5
  60. package/dist/src/extract/services/component-deduplicator.d.ts +0 -2
  61. package/dist/src/extract/services/extraction-pipeline.d.ts +0 -3
  62. package/dist/src/extract/services/extraction-pipeline.js +0 -43
  63. package/dist/src/extract/services/extraction-runner.d.ts +0 -4
  64. package/dist/src/extract/services/extraction-runner.js +0 -21
  65. package/dist/src/extract/services/extraction-service.d.ts +0 -13
  66. package/dist/src/extract/services/extraction-service.js +0 -17
  67. package/dist/src/extract/services/extractor-registry.d.ts +0 -8
  68. package/dist/src/extract/services/extractor-registry.js +0 -68
  69. package/dist/src/extract/services/ports/component-extractor.d.ts +0 -7
  70. package/dist/src/extract/services/ports/component-extractor.js +0 -1
  71. package/dist/src/extract/services/quality-service.d.ts +0 -4
  72. package/dist/src/extract/services/quality-service.js +0 -61
  73. /package/dist/src/extract/{policies/quality/authorability.js → non-authorable-filter.js} +0 -0
  74. /package/dist/src/extract/{evidence/source-evidence.d.ts → parse-imported-names.d.ts} +0 -0
  75. /package/dist/src/extract/{evidence/source-evidence.js → parse-imported-names.js} +0 -0
  76. /package/dist/src/extract/{adapters/support/resolution/local-module.d.ts → resolve-local-module.d.ts} +0 -0
  77. /package/dist/src/extract/{adapters/support/resolution/local-module.js → resolve-local-module.js} +0 -0
  78. /package/dist/src/extract/{adapters/support/resolution/type-property.d.ts → resolve-type-property.d.ts} +0 -0
  79. /package/dist/src/extract/{adapters/support/resolution/type-property.js → resolve-type-property.js} +0 -0
  80. /package/dist/src/extract/{evidence/allowed-components.d.ts → slot-allowed-components.d.ts} +0 -0
  81. /package/dist/src/extract/{evidence/allowed-components.js → slot-allowed-components.js} +0 -0
  82. /package/dist/src/extract/{evidence/slot-evidence.d.ts → slot-detection.d.ts} +0 -0
  83. /package/dist/src/extract/{evidence/slot-evidence.js → slot-detection.js} +0 -0
  84. /package/dist/src/extract/{adapters/support/resolution/source-location.js → source-line-metadata.js} +0 -0
  85. /package/dist/src/extract/{adapters/support/tsx-shared.js → tsx-shared.js} +0 -0
  86. /package/dist/src/extract/{policies/quality/validation.js → validate.js} +0 -0
  87. /package/dist/src/{extract/services/classification-service.js → pre-classify.js} +0 -0
  88. /package/dist/src/{extract/model/component.js → types.js} +0 -0
@@ -1,11 +1,9 @@
1
1
  import { Node, SyntaxKind, } from 'ts-morph';
2
- import { extractAllowedValues, getNodeDefinitions, getTypeReferenceName, getTypeTargetDeclarations, getJsxTagNameNode, extractTsxComponents, getRenderableExports, } from '../support/tsx-shared.js';
3
- import { shouldBeSlot } from '../../evidence/slot-evidence.js';
4
- import { extractAllowedComponentsFromTypeText, extractAllowedComponentsFromJsdoc, } from '../../evidence/allowed-components.js';
5
- import { getSourceLineMetadata } from '../support/resolution/source-location.js';
6
- import { collectTypePredicateComponentReferences, collectRuntimeTypeCheckComponentReferences, collectRenderedComponentReferences, collectArrayMapRenderComponentReferences, } from '../../evidence/structural-slot-evidence.js';
7
- import { collectPropDataflow, getComponentIdentity } from './prop-dataflow.js';
8
- import { resolveBestFunctionNode } from './function-resolution.js';
2
+ import { extractAllowedValues, getNodeDefinitions, getTypeReferenceName, getTypeTargetDeclarations, getValueTargetDeclarations, getJsxTagNameNode, extractTsxComponents, getRenderableExports, isIntrinsicJsxElement, } from './tsx-shared.js';
3
+ import { shouldBeSlot } from './slot-detection.js';
4
+ import { extractAllowedComponentsFromTypeText, extractAllowedComponentsFromJsdoc } from './slot-allowed-components.js';
5
+ import { getSourceLineMetadata } from './source-line-metadata.js';
6
+ import { collectTypePredicateComponentReferences, collectRuntimeTypeCheckComponentReferences, collectRenderedComponentReferences, collectArrayMapRenderComponentReferences, } from './structural-slot-evidence.js';
9
7
  const REACT_ELEMENT_GENERIC_TEST = /(?:React\.)?ReactElement\s*<\s*[A-Za-z_$][\w$.]*/;
10
8
  function isReactElementGenericSlotType(typeText) {
11
9
  return REACT_ELEMENT_GENERIC_TEST.test(typeText);
@@ -819,6 +817,223 @@ function filterImplementationOnlyAliasProps(props, func) {
819
817
  return !isImplementationOnlyAliasProp(func, prop.name);
820
818
  });
821
819
  }
820
+ function getDeclarationIdentity(node) {
821
+ if (Node.isIdentifier(node)) {
822
+ const parent = node.getParent();
823
+ if ((Node.isVariableDeclaration(parent) || Node.isBindingElement(parent) || Node.isParameterDeclaration(parent)) &&
824
+ parent.getNameNode() === node) {
825
+ return parent;
826
+ }
827
+ }
828
+ return getNodeDefinitions(node)[0]?.getDeclarationNode() ?? node;
829
+ }
830
+ function getComponentIdentity(func) {
831
+ return `${func.getSourceFile().getFilePath()}:${func.getStart()}`;
832
+ }
833
+ function resolveForwardedComponentIdentity(tagNameNode) {
834
+ if (!Node.isIdentifier(tagNameNode))
835
+ return undefined;
836
+ for (const declaration of getValueTargetDeclarations(tagNameNode)) {
837
+ const func = resolveFunctionNode(declaration);
838
+ if (func)
839
+ return getComponentIdentity(func);
840
+ }
841
+ return undefined;
842
+ }
843
+ /**
844
+ * Declarations that are rebound somewhere in the body, so their initializer no
845
+ * longer describes the value that reaches JSX.
846
+ */
847
+ function collectReboundDeclarations(body) {
848
+ const rebound = new Set();
849
+ const recordTarget = (target) => {
850
+ if (Node.isIdentifier(target)) {
851
+ rebound.add(getDeclarationIdentity(target));
852
+ return;
853
+ }
854
+ if ((Node.isPropertyAccessExpression(target) || Node.isElementAccessExpression(target)) &&
855
+ Node.isIdentifier(target.getExpression())) {
856
+ rebound.add(getDeclarationIdentity(target.getExpression()));
857
+ }
858
+ };
859
+ for (const assignment of body?.getDescendantsOfKind(SyntaxKind.BinaryExpression) ?? []) {
860
+ // Covers `=` and every compound form (`+=`, `??=`, `>>>=`, ...).
861
+ const operator = assignment.getOperatorToken().getKind();
862
+ if (operator < SyntaxKind.FirstAssignment || operator > SyntaxKind.LastAssignment)
863
+ continue;
864
+ recordTarget(assignment.getLeft());
865
+ }
866
+ // Only `++`/`--` rebind the operand. Reading a prop through `!`, `-`, `+`, or
867
+ // `~` leaves the alias intact, so those operators must not invalidate it.
868
+ for (const update of body?.getDescendantsOfKind(SyntaxKind.PrefixUnaryExpression) ?? []) {
869
+ const operator = update.getOperatorToken();
870
+ if (operator !== SyntaxKind.PlusPlusToken && operator !== SyntaxKind.MinusMinusToken)
871
+ continue;
872
+ recordTarget(update.getOperand());
873
+ }
874
+ for (const update of body?.getDescendantsOfKind(SyntaxKind.PostfixUnaryExpression) ?? []) {
875
+ recordTarget(update.getOperand());
876
+ }
877
+ return rebound;
878
+ }
879
+ /** Trace only statically resolvable, component-local prop dataflow into JSX. */
880
+ function collectPropDataflow(func, param, availablePropNames) {
881
+ const valueAliases = new Map();
882
+ const objectAliases = new Map();
883
+ const fullPropsShape = new Map([...availablePropNames].map((name) => [name, name]));
884
+ const belongsToComponentFunction = (node) => node.getFirstAncestor((ancestor) => Node.isFunctionDeclaration(ancestor) || Node.isArrowFunction(ancestor) || Node.isFunctionExpression(ancestor)) === func;
885
+ const resolveValue = (expression) => {
886
+ if (!expression)
887
+ return undefined;
888
+ if (Node.isParenthesizedExpression(expression))
889
+ return resolveValue(expression.getExpression());
890
+ if (Node.isIdentifier(expression))
891
+ return valueAliases.get(getDeclarationIdentity(expression));
892
+ if (Node.isPropertyAccessExpression(expression)) {
893
+ const object = expression.getExpression();
894
+ return Node.isIdentifier(object)
895
+ ? objectAliases.get(getDeclarationIdentity(object))?.get(expression.getName())
896
+ : undefined;
897
+ }
898
+ if (Node.isElementAccessExpression(expression)) {
899
+ const argument = expression.getArgumentExpression();
900
+ if (!argument || (!Node.isStringLiteral(argument) && !Node.isNoSubstitutionTemplateLiteral(argument))) {
901
+ return undefined;
902
+ }
903
+ const object = expression.getExpression();
904
+ return Node.isIdentifier(object)
905
+ ? objectAliases.get(getDeclarationIdentity(object))?.get(argument.getLiteralText())
906
+ : undefined;
907
+ }
908
+ return undefined;
909
+ };
910
+ const resolveObject = (expression) => {
911
+ if (!expression)
912
+ return undefined;
913
+ if (Node.isParenthesizedExpression(expression))
914
+ return resolveObject(expression.getExpression());
915
+ if (Node.isIdentifier(expression))
916
+ return objectAliases.get(getDeclarationIdentity(expression));
917
+ if (!Node.isObjectLiteralExpression(expression))
918
+ return undefined;
919
+ const shape = new Map();
920
+ for (const property of expression.getProperties()) {
921
+ if (Node.isSpreadAssignment(property)) {
922
+ const spreadShape = resolveObject(property.getExpression());
923
+ for (const [targetProp, sourceProp] of spreadShape ?? [])
924
+ shape.set(targetProp, sourceProp);
925
+ }
926
+ else if (Node.isShorthandPropertyAssignment(property)) {
927
+ const sourceProp = resolveValue(property.getNameNode());
928
+ if (sourceProp)
929
+ shape.set(property.getName(), sourceProp);
930
+ }
931
+ else if (Node.isPropertyAssignment(property)) {
932
+ const propertyNameNode = property.getNameNode();
933
+ if (!Node.isIdentifier(propertyNameNode) && !Node.isStringLiteral(propertyNameNode))
934
+ continue;
935
+ const sourceProp = resolveValue(property.getInitializer());
936
+ if (sourceProp)
937
+ shape.set(property.getName(), sourceProp);
938
+ }
939
+ }
940
+ return shape;
941
+ };
942
+ const addBindings = (pattern, sourceShape) => {
943
+ const omittedProps = new Set(pattern
944
+ .getElements()
945
+ .filter((element) => !element.getDotDotDotToken())
946
+ .map((element) => element.getPropertyNameNode()?.getText() ?? element.getNameNode().getText()));
947
+ for (const element of pattern.getElements()) {
948
+ const localName = element.getNameNode();
949
+ if (!Node.isIdentifier(localName))
950
+ continue;
951
+ if (element.getDotDotDotToken()) {
952
+ objectAliases.set(getDeclarationIdentity(localName), new Map([...sourceShape].filter(([targetProp]) => !omittedProps.has(targetProp))));
953
+ continue;
954
+ }
955
+ const targetProp = element.getPropertyNameNode()?.getText() ?? localName.getText();
956
+ const sourceProp = sourceShape.get(targetProp);
957
+ if (sourceProp)
958
+ valueAliases.set(getDeclarationIdentity(localName), sourceProp);
959
+ }
960
+ };
961
+ const paramNameNode = param.getNameNode();
962
+ if (Node.isIdentifier(paramNameNode))
963
+ objectAliases.set(getDeclarationIdentity(paramNameNode), fullPropsShape);
964
+ else if (Node.isObjectBindingPattern(paramNameNode))
965
+ addBindings(paramNameNode, fullPropsShape);
966
+ const body = func.getBody();
967
+ const reboundDeclarations = collectReboundDeclarations(body);
968
+ for (const declaration of reboundDeclarations) {
969
+ valueAliases.delete(declaration);
970
+ objectAliases.delete(declaration);
971
+ }
972
+ for (const declaration of body?.getDescendantsOfKind(SyntaxKind.VariableDeclaration) ?? []) {
973
+ if (!belongsToComponentFunction(declaration))
974
+ continue;
975
+ const initializer = declaration.getInitializer();
976
+ const declarationName = declaration.getNameNode();
977
+ if (Node.isIdentifier(declarationName)) {
978
+ const aliasDeclaration = getDeclarationIdentity(declarationName);
979
+ if (reboundDeclarations.has(aliasDeclaration))
980
+ continue;
981
+ const sourceProp = resolveValue(initializer);
982
+ if (sourceProp)
983
+ valueAliases.set(aliasDeclaration, sourceProp);
984
+ const sourceShape = resolveObject(initializer);
985
+ if (sourceShape)
986
+ objectAliases.set(aliasDeclaration, new Map(sourceShape));
987
+ }
988
+ else if (Node.isObjectBindingPattern(declarationName)) {
989
+ const sourceShape = resolveObject(initializer);
990
+ if (sourceShape) {
991
+ const hasInvalidatedBinding = declarationName
992
+ .getElements()
993
+ .some((element) => reboundDeclarations.has(getDeclarationIdentity(element.getNameNode())));
994
+ if (!hasInvalidatedBinding)
995
+ addBindings(declarationName, sourceShape);
996
+ }
997
+ }
998
+ }
999
+ const intrinsicDomProps = new Set();
1000
+ const componentEdges = [];
1001
+ const recordForwarding = (tagNameNode, targetProp, sourceProp) => {
1002
+ const tagName = tagNameNode.getText();
1003
+ if (isIntrinsicJsxElement(tagName)) {
1004
+ if (targetProp === sourceProp)
1005
+ intrinsicDomProps.add(sourceProp);
1006
+ return;
1007
+ }
1008
+ const targetComponentIdentity = resolveForwardedComponentIdentity(tagNameNode);
1009
+ if (targetComponentIdentity)
1010
+ componentEdges.push({ sourceProp, targetComponentIdentity, targetProp });
1011
+ };
1012
+ for (const attribute of func.getDescendantsOfKind(SyntaxKind.JsxAttribute)) {
1013
+ if (!belongsToComponentFunction(attribute))
1014
+ continue;
1015
+ const tagNameNode = getJsxTagNameNode(attribute);
1016
+ if (!tagNameNode)
1017
+ continue;
1018
+ const initializer = attribute.getInitializer();
1019
+ if (!initializer || !Node.isJsxExpression(initializer))
1020
+ continue;
1021
+ const sourceProp = resolveValue(initializer.getExpression());
1022
+ if (sourceProp)
1023
+ recordForwarding(tagNameNode, attribute.getNameNode().getText(), sourceProp);
1024
+ }
1025
+ for (const spread of func.getDescendantsOfKind(SyntaxKind.JsxSpreadAttribute)) {
1026
+ if (!belongsToComponentFunction(spread))
1027
+ continue;
1028
+ const tagNameNode = getJsxTagNameNode(spread);
1029
+ if (!tagNameNode)
1030
+ continue;
1031
+ const shape = resolveObject(spread.getExpression());
1032
+ for (const [targetProp, sourceProp] of shape ?? [])
1033
+ recordForwarding(tagNameNode, targetProp, sourceProp);
1034
+ }
1035
+ return { intrinsicDomProps, componentEdges };
1036
+ }
822
1037
  function extractDestructuredBindingFallbackProps(func, param, existingPropNames, slotNames) {
823
1038
  const nameNode = param.getNameNode();
824
1039
  const bindingPatterns = [];
@@ -1448,8 +1663,8 @@ export async function extractReactComponents(filePaths) {
1448
1663
  }
1449
1664
  }
1450
1665
  }
1451
- // Structural evidence (usage, not a declared slot contract) — see the
1452
- // structural slot evidence pass. Only fills slots the typed-slot pass above
1666
+ // Structural evidence (usage, not a declared slot contract) — see
1667
+ // structural-slot-evidence.ts. Only fills slots the typed-slot pass above
1453
1668
  // found nothing for; a declared contract always takes precedence. File-wide
1454
1669
  // signals (type-predicate functions, `.type === Component` identity checks)
1455
1670
  // are computed once per file and shared across every component in it, since
@@ -1723,3 +1938,132 @@ function extractPropTypes(sourceFile, componentName) {
1723
1938
  }
1724
1939
  return undefined;
1725
1940
  }
1941
+ function resolveFunctionNode(decl) {
1942
+ if (Node.isFunctionDeclaration(decl))
1943
+ return decl;
1944
+ if (Node.isArrowFunction(decl))
1945
+ return decl;
1946
+ if (Node.isFunctionExpression(decl))
1947
+ return decl;
1948
+ if (Node.isVariableDeclaration(decl)) {
1949
+ const init = decl.getInitializer();
1950
+ if (init && Node.isArrowFunction(init))
1951
+ return init;
1952
+ if (init && Node.isFunctionExpression(init))
1953
+ return init;
1954
+ if (init && Node.isCallExpression(init))
1955
+ return resolveFunctionFromCallExpression(init, new Set());
1956
+ }
1957
+ return undefined;
1958
+ }
1959
+ function resolveBestFunctionNode(declarations) {
1960
+ const candidates = declarations
1961
+ .map((decl) => resolveFunctionNode(decl))
1962
+ .filter((candidate) => candidate !== undefined);
1963
+ const implementation = candidates.find((candidate) => !Node.isFunctionDeclaration(candidate) || candidate.getBody() !== undefined);
1964
+ return implementation ?? candidates[0];
1965
+ }
1966
+ function resolveForwardRefFunction(callExpr) {
1967
+ if (!Node.isCallExpression(callExpr))
1968
+ return undefined;
1969
+ const expressionText = callExpr.getExpression().getText();
1970
+ if (!/(^|\.)forwardRef$/.test(expressionText))
1971
+ return undefined;
1972
+ const firstArg = callExpr.getArguments()[0];
1973
+ if (!firstArg)
1974
+ return undefined;
1975
+ if (Node.isArrowFunction(firstArg))
1976
+ return firstArg;
1977
+ if (Node.isFunctionExpression(firstArg))
1978
+ return firstArg;
1979
+ if (Node.isIdentifier(firstArg)) {
1980
+ const declarations = getNodeDefinitions(firstArg).flatMap((definition) => {
1981
+ const declarationNode = definition.getDeclarationNode();
1982
+ return declarationNode ? [declarationNode] : [];
1983
+ });
1984
+ for (const declaration of declarations) {
1985
+ const resolved = resolveFunctionNode(declaration);
1986
+ if (resolved)
1987
+ return resolved;
1988
+ }
1989
+ }
1990
+ return undefined;
1991
+ }
1992
+ function resolveFunctionFromCallExpression(callExpr, seen) {
1993
+ return resolveForwardRefFunction(callExpr) ?? resolveReturnedFunctionFromFactoryCall(callExpr, seen);
1994
+ }
1995
+ function resolveReturnedFunctionFromFactoryCall(callExpr, seen) {
1996
+ const expression = callExpr.getExpression();
1997
+ if (!Node.isIdentifier(expression))
1998
+ return undefined;
1999
+ for (const definition of getNodeDefinitions(expression)) {
2000
+ const declaration = definition.getDeclarationNode();
2001
+ if (!declaration)
2002
+ continue;
2003
+ const resolved = resolveReturnedFunctionFromDeclaration(declaration, seen);
2004
+ if (resolved)
2005
+ return resolved;
2006
+ }
2007
+ return undefined;
2008
+ }
2009
+ function resolveReturnedFunctionFromDeclaration(node, seen) {
2010
+ if (seen.has(node))
2011
+ return undefined;
2012
+ seen.add(node);
2013
+ if (Node.isVariableDeclaration(node)) {
2014
+ const initializer = node.getInitializer();
2015
+ if (!initializer)
2016
+ return undefined;
2017
+ if (Node.isArrowFunction(initializer) || Node.isFunctionExpression(initializer)) {
2018
+ return resolveReturnedFunctionFromCallable(initializer, seen);
2019
+ }
2020
+ if (Node.isCallExpression(initializer)) {
2021
+ return resolveFunctionFromCallExpression(initializer, seen);
2022
+ }
2023
+ return undefined;
2024
+ }
2025
+ if (Node.isFunctionDeclaration(node) || Node.isFunctionExpression(node) || Node.isArrowFunction(node)) {
2026
+ return resolveReturnedFunctionFromCallable(node, seen);
2027
+ }
2028
+ return undefined;
2029
+ }
2030
+ function resolveReturnedFunctionFromCallable(fn, seen) {
2031
+ const body = fn.getBody();
2032
+ if (!body)
2033
+ return undefined;
2034
+ if (!Node.isBlock(body)) {
2035
+ return resolveReturnedFunctionFromExpression(body, seen);
2036
+ }
2037
+ for (const statement of body.getStatements()) {
2038
+ if (!Node.isReturnStatement(statement))
2039
+ continue;
2040
+ const expression = statement.getExpression();
2041
+ if (!expression)
2042
+ continue;
2043
+ const resolved = resolveReturnedFunctionFromExpression(expression, seen);
2044
+ if (resolved)
2045
+ return resolved;
2046
+ }
2047
+ return undefined;
2048
+ }
2049
+ function resolveReturnedFunctionFromExpression(node, seen) {
2050
+ if (Node.isArrowFunction(node) || Node.isFunctionExpression(node))
2051
+ return node;
2052
+ if (Node.isParenthesizedExpression(node)) {
2053
+ return resolveReturnedFunctionFromExpression(node.getExpression(), seen);
2054
+ }
2055
+ if (Node.isCallExpression(node)) {
2056
+ return resolveFunctionFromCallExpression(node, seen);
2057
+ }
2058
+ if (Node.isIdentifier(node)) {
2059
+ for (const definition of getNodeDefinitions(node)) {
2060
+ const declaration = definition.getDeclarationNode();
2061
+ if (!declaration)
2062
+ continue;
2063
+ const resolved = resolveReturnedFunctionFromDeclaration(declaration, seen);
2064
+ if (resolved)
2065
+ return resolved;
2066
+ }
2067
+ }
2068
+ return undefined;
2069
+ }
@@ -0,0 +1,12 @@
1
+ import type { RawComponentDefinition } from '../types.js';
2
+ export type ExtractionConfidence = 1 | 2 | 3 | 4 | 5;
3
+ export type ExtractionScore = {
4
+ confidence: ExtractionConfidence;
5
+ reasons: string[];
6
+ };
7
+ export interface ExtractionScoreOptions {
8
+ additionalIssueCount?: number;
9
+ additionalReasons?: string[];
10
+ }
11
+ export declare function computeExtractionScore(component: RawComponentDefinition, options?: ExtractionScoreOptions): ExtractionScore;
12
+ export declare function deriveNeedsReview(confidence: ExtractionConfidence): boolean;
@@ -1,4 +1,3 @@
1
- // 1 = very low confidence (likely wrong), 5 = very high confidence (clearly correct)
2
1
  // Prop type strings that indicate the extractor couldn't resolve a concrete type
3
2
  const OPAQUE_TYPES = new Set(['any', 'unknown', 'object', 'Record<string, unknown>', 'Record<string, any>']);
4
3
  // Prop names that are non-obvious and benefit from a description
@@ -1,4 +1,4 @@
1
- import type { RawComponentDefinition } from '../../model/component.js';
1
+ import type { RawComponentDefinition } from '../types.js';
2
2
  export declare const HIGH_CONFIDENCE_DATA_FETCH_WRAPPER_REASON = "data-fetch-wrapper";
3
3
  export declare const POSSIBLE_DATA_FETCH_WRAPPER_REASON = "possible-data-fetch-wrapper";
4
4
  export declare const ZERO_SURFACE_RENDERED_UI_REASON = "zero-surface:rendered-ui";
@@ -1,5 +1,5 @@
1
1
  import { readFile } from 'node:fs/promises';
2
- import { parseImportedNames } from '../../evidence/source-evidence.js';
2
+ import { parseImportedNames } from './parse-imported-names.js';
3
3
  export const HIGH_CONFIDENCE_DATA_FETCH_WRAPPER_REASON = 'data-fetch-wrapper';
4
4
  export const POSSIBLE_DATA_FETCH_WRAPPER_REASON = 'possible-data-fetch-wrapper';
5
5
  export const ZERO_SURFACE_RENDERED_UI_REASON = 'zero-surface:rendered-ui';
@@ -1,3 +1,3 @@
1
1
  import type { Node } from 'ts-morph';
2
- import type { RawPropDefinition } from '../../../model/component.js';
2
+ import type { RawPropDefinition } from '../types.js';
3
3
  export declare function getSourceLineMetadata(declaration: Node | undefined): Pick<RawPropDefinition, 'sourceStartLine' | 'sourceEndLine'>;
@@ -1,2 +1,2 @@
1
- import type { ComponentExtractionResult } from '../../model/component.js';
1
+ import type { ComponentExtractionResult } from '../types.js';
2
2
  export declare function extractStencilComponents(filePaths: string[]): Promise<ComponentExtractionResult>;
@@ -1,7 +1,6 @@
1
1
  import { Project, Node, SyntaxKind } from 'ts-morph';
2
- import { getJsxTagNameNode, isIntrinsicJsxElement, kebabToPascal } from '../support/tsx-shared.js';
3
- import { createSortedExtractionResult } from '../support/file-processing/result-normalizer.js';
4
- import { extractProjectSourceFiles } from '../support/file-processing/project-source-files.js';
2
+ import { getJsxTagNameNode, isIntrinsicJsxElement, kebabToPascal } from './tsx-shared.js';
3
+ import { createSortedExtractionResult, extractProjectSourceFiles } from './file-extraction-workers.js';
5
4
  function isStencilFile(sourceFile) {
6
5
  return sourceFile.getImportDeclarations().some((imp) => imp.getModuleSpecifierValue() === '@stencil/core');
7
6
  }
@@ -1,5 +1,5 @@
1
1
  import { type FunctionDeclaration, type ArrowFunction, type FunctionExpression, type SourceFile } from 'ts-morph';
2
- import { type AllowedComponentsContext } from './allowed-components.js';
2
+ import { type AllowedComponentsContext } from './slot-allowed-components.js';
3
3
  type FunctionLike = FunctionDeclaration | ArrowFunction | FunctionExpression;
4
4
  /**
5
5
  * Structural composition evidence: parent→child relationships a component's
@@ -1,6 +1,6 @@
1
1
  import { Node, SyntaxKind, } from 'ts-morph';
2
- import { extractAllowedComponentsFromTypeText } from './allowed-components.js';
3
- import { isIntrinsicJsxElement } from '../adapters/support/tsx-shared.js';
2
+ import { extractAllowedComponentsFromTypeText } from './slot-allowed-components.js';
3
+ import { isIntrinsicJsxElement } from './tsx-shared.js';
4
4
  /**
5
5
  * Structural composition evidence: parent→child relationships a component's
6
6
  * source implies through *usage*, not through a declared slot contract
@@ -1,5 +1,4 @@
1
- import type { ComponentExtractionResult } from '../../model/component.js';
2
- import type { ExtractorOptions } from '../../model/options.js';
1
+ import type { ComponentExtractionResult, ExtractorOptions } from '../types.js';
3
2
  export declare function extractSvelteComponents(filePaths: string[], onProgress?: (p: {
4
3
  filesProcessed: number;
5
4
  componentsFound: number;
@@ -1,15 +1,13 @@
1
1
  import os from 'node:os';
2
- import { dirname, resolve, join } from 'node:path';
2
+ import { basename, dirname, resolve, join } from 'node:path';
3
3
  import { existsSync, readFileSync } from 'node:fs';
4
4
  import { createRequire } from 'node:module';
5
5
  import { parse as parseSvelte } from 'svelte/compiler';
6
6
  import { Project, Node, ScriptTarget, ModuleKind, ts } from 'ts-morph';
7
- import { computeExtractionScore, deriveNeedsReview } from '../../policies/quality/scoring.js';
8
- import { extractAllowedComponentsFromTypeText } from '../../evidence/allowed-components.js';
9
- import { runFileExtractionWorkers } from '../support/file-processing/file-workers.js';
10
- import { resolveLocalModule } from '../support/resolution/local-module.js';
11
- import { getSvelteComponentName } from './identity.js';
12
- import { extractTemplateSlots, mergeSlots } from './slots.js';
7
+ import { computeExtractionScore, deriveNeedsReview } from './scoring.js';
8
+ import { extractAllowedComponentsFromTypeText } from './slot-allowed-components.js';
9
+ import { runFileExtractionWorkers } from './file-extraction-workers.js';
10
+ import { resolveLocalModule } from './resolve-local-module.js';
13
11
  export async function extractSvelteComponents(filePaths, onProgress, opts) {
14
12
  const svelteFiles = filePaths.filter((f) => f.endsWith('.svelte'));
15
13
  const retryContexts = new Map();
@@ -468,6 +466,31 @@ async function extractFromSvelteFile(filePath, source) {
468
466
  }
469
467
  return { component, warnings, ...(retryContext ? { retryContext } : {}) };
470
468
  }
469
+ const ANATOMY_FOLDERS = new Set(['anatomy', 'parts']);
470
+ function getSvelteComponentName(filePath) {
471
+ const file = basename(filePath, '.svelte');
472
+ const parentDir = basename(dirname(filePath));
473
+ if (file === 'index')
474
+ return toPascalCase(parentDir);
475
+ if (ANATOMY_FOLDERS.has(parentDir)) {
476
+ const grandparent = basename(dirname(dirname(filePath)));
477
+ if (grandparent && grandparent !== '.' && grandparent !== '/') {
478
+ return `${toPascalCase(grandparent)}${toPascalCase(file)}`;
479
+ }
480
+ }
481
+ return toPascalCase(file);
482
+ }
483
+ function toPascalCase(s) {
484
+ if (!s)
485
+ return s;
486
+ if (/^[A-Z]/.test(s) && !s.includes('-') && !s.includes('_'))
487
+ return s;
488
+ return s
489
+ .split(/[-_]+/)
490
+ .filter(Boolean)
491
+ .map((part) => part[0].toUpperCase() + part.slice(1))
492
+ .join('');
493
+ }
471
494
  function hasV4ExportLetProps(instance) {
472
495
  const body = instance['content']?.['body'];
473
496
  if (!body)
@@ -1223,3 +1246,64 @@ function extractAllowedValuesFromText(typeText) {
1223
1246
  }
1224
1247
  return out.length > 0 ? out : undefined;
1225
1248
  }
1249
+ function extractTemplateSlots(fragment) {
1250
+ const slots = [];
1251
+ const seen = new Set();
1252
+ walk(fragment);
1253
+ return slots;
1254
+ function walk(node) {
1255
+ if (!node)
1256
+ return;
1257
+ if (node.type === 'SlotElement') {
1258
+ const name = readSlotName(node);
1259
+ if (!seen.has(name)) {
1260
+ seen.add(name);
1261
+ slots.push({ name, isDefault: name === 'default' });
1262
+ }
1263
+ }
1264
+ const children = node['nodes'] ?? node['children'] ?? [];
1265
+ for (const c of children)
1266
+ walk(c);
1267
+ const fragChild = node['fragment'];
1268
+ if (fragChild)
1269
+ walk(fragChild);
1270
+ }
1271
+ }
1272
+ function readSlotName(slotEl) {
1273
+ const attrs = slotEl['attributes'] ?? [];
1274
+ for (const attr of attrs) {
1275
+ if (attr['name'] !== 'name')
1276
+ continue;
1277
+ const value = attr['value'];
1278
+ if (Array.isArray(value)) {
1279
+ for (const v of value) {
1280
+ if (v.type === 'Text' && typeof v['data'] === 'string')
1281
+ return v['data'];
1282
+ }
1283
+ }
1284
+ }
1285
+ return 'default';
1286
+ }
1287
+ function mergeSlots(fromSnippetProps, fromTemplate) {
1288
+ if (fromSnippetProps.length === 0)
1289
+ return { slots: fromTemplate, mixedWarning: false };
1290
+ if (fromTemplate.length === 0)
1291
+ return { slots: fromSnippetProps, mixedWarning: false };
1292
+ const byName = new Map();
1293
+ for (const s of fromSnippetProps)
1294
+ byName.set(s.name, s);
1295
+ const snippetHasDefault = fromSnippetProps.some((s) => s.isDefault);
1296
+ let mixed = false;
1297
+ for (const s of fromTemplate) {
1298
+ if (s.isDefault && snippetHasDefault) {
1299
+ mixed = true;
1300
+ continue;
1301
+ }
1302
+ if (byName.has(s.name)) {
1303
+ mixed = true;
1304
+ continue;
1305
+ }
1306
+ byName.set(s.name, s);
1307
+ }
1308
+ return { slots: [...byName.values()], mixedWarning: mixed };
1309
+ }
@@ -1,5 +1,5 @@
1
1
  import { Node, Project, type SourceFile, type Type } from 'ts-morph';
2
- import type { ExtractionExclusion } from '../../model/component.js';
2
+ import type { ExtractionExclusion } from '../types.js';
3
3
  export declare function getTsxExtractionContext(filePaths: string[], componentFilePattern: RegExp): {
4
4
  componentFiles: string[];
5
5
  project: Project;
@@ -1,4 +1,4 @@
1
- import type { RawComponentDefinition, ExtractionValidationIssue } from '../../model/component.js';
1
+ import type { RawComponentDefinition, ExtractionValidationIssue } from '../types.js';
2
2
  export declare function validateExtractedComponents(components: RawComponentDefinition[]): RawComponentDefinition[];
3
3
  export declare function shouldExcludeDueToValidation(component: RawComponentDefinition): boolean;
4
4
  /**
@@ -1,2 +1,2 @@
1
- import type { ComponentExtractionResult } from '../../model/component.js';
1
+ import type { ComponentExtractionResult } from '../types.js';
2
2
  export declare function extractVueTsxComponents(filePaths: string[]): Promise<ComponentExtractionResult>;
@@ -1,5 +1,5 @@
1
1
  import { Node, SyntaxKind } from 'ts-morph';
2
- import { extractAllowedValues, extractTsxComponents, getTypeReferenceName, getTypeTargetDeclarations, getValueTargetDeclarations, getRenderableExports, } from '../support/tsx-shared.js';
2
+ import { extractAllowedValues, extractTsxComponents, getTypeReferenceName, getTypeTargetDeclarations, getValueTargetDeclarations, getRenderableExports, } from './tsx-shared.js';
3
3
  export async function extractVueTsxComponents(filePaths) {
4
4
  const { components, warnings, exclusions } = extractTsxComponents(filePaths, /\.tsx$/, extractFromSourceFile);
5
5
  return {
@@ -1,4 +1,4 @@
1
- import type { ComponentExtractionResult } from '../../model/component.js';
1
+ import type { ComponentExtractionResult } from '../types.js';
2
2
  export declare function extractVueComponents(filePaths: string[], onProgress?: (p: {
3
3
  filesProcessed: number;
4
4
  componentsFound: number;
@@ -4,10 +4,9 @@ import { readFile } from 'node:fs/promises';
4
4
  import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
5
5
  import { parse as parseSFC } from '@vue/compiler-sfc';
6
6
  import { Project, Node } from 'ts-morph';
7
- import { runFileExtractionWorkers } from '../support/file-processing/file-workers.js';
8
- import { createSortedExtractionResult } from '../support/file-processing/result-normalizer.js';
9
- import { resolveLocalModule } from '../support/resolution/local-module.js';
10
- import { resolveTypeProperty } from '../support/resolution/type-property.js';
7
+ import { createSortedExtractionResult, runFileExtractionWorkers } from './file-extraction-workers.js';
8
+ import { resolveLocalModule } from './resolve-local-module.js';
9
+ import { resolveTypeProperty } from './resolve-type-property.js';
11
10
  // @vue/compiler-core NodeTypes enum values (stable since Vue 3.0)
12
11
  const ELEMENT_TYPE = 1;
13
12
  const ATTRIBUTE_TYPE = 6;
@@ -1,2 +1,2 @@
1
- import type { ComponentExtractionResult } from '../../model/component.js';
1
+ import type { ComponentExtractionResult } from '../types.js';
2
2
  export declare function extractWebComponentDefinitions(filePaths: string[]): Promise<ComponentExtractionResult>;