@codeyam/codeyam-cli 0.1.0-staging.323686 → 0.1.0-staging.483fdc2
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/analyzer-template/.build-info.json +7 -7
- package/analyzer-template/log.txt +3 -3
- package/analyzer-template/package.json +2 -2
- package/analyzer-template/packages/ai/index.ts +6 -1
- package/analyzer-template/packages/ai/src/lib/analyzeScope.ts +39 -17
- package/analyzer-template/packages/ai/src/lib/astScopes/astScopeAnalyzer.ts +67 -9
- package/analyzer-template/packages/ai/src/lib/astScopes/processExpression.ts +308 -50
- package/analyzer-template/packages/ai/src/lib/astScopes/types.ts +15 -6
- package/analyzer-template/packages/ai/src/lib/dataStructure/ScopeDataStructure.ts +664 -242
- package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/BatchSchemaProcessor.ts +16 -3
- package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/ScopeTreeManager.ts +6 -4
- package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.ts +20 -1
- package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/cleanNonObjectFunctions.ts +35 -13
- package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/convertTypeAnnotationsToValues.ts +160 -0
- package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/deduplicateFunctionSchemas.ts +40 -30
- package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/fillInSchemaGapsAndUnknowns.ts +289 -83
- package/analyzer-template/packages/ai/src/lib/generateEntityScenarioData.ts +269 -1
- package/analyzer-template/packages/ai/src/lib/generateEntityScenarios.ts +9 -5
- package/analyzer-template/packages/ai/src/lib/generateExecutionFlows.ts +11 -3
- package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromConditionalEffects.ts +1 -1
- package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromConditionals.ts +297 -7
- package/analyzer-template/packages/ai/src/lib/generateExecutionFlowsFromJsxUsages.ts +1 -1
- package/analyzer-template/packages/ai/src/lib/mergeStatements.ts +90 -96
- package/analyzer-template/packages/ai/src/lib/promptGenerators/gatherAttributesMap.ts +10 -7
- package/analyzer-template/packages/ai/src/lib/resolvePathToControllable.ts +25 -13
- package/analyzer-template/packages/ai/src/lib/worker/SerializableDataStructure.ts +4 -3
- package/analyzer-template/packages/analyze/src/lib/FileAnalyzer.ts +65 -59
- package/analyzer-template/packages/analyze/src/lib/ProjectAnalyzer.ts +113 -26
- package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getAllDeclaredEntityNodes.ts +19 -0
- package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getAllEntityNodes.ts +19 -0
- package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getAllExports.ts +11 -0
- package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getImportsAnalysis.ts +8 -0
- package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getResolvedModule.ts +49 -1
- package/analyzer-template/packages/analyze/src/lib/asts/sourceFiles/getSourceFilesForAllImports.ts +2 -1
- package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.ts +20 -6
- package/analyzer-template/packages/analyze/src/lib/files/analyze/analyzeEntities.ts +14 -4
- package/analyzer-template/packages/analyze/src/lib/files/analyze/gatherEntityMap.ts +4 -2
- package/analyzer-template/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.ts +0 -3
- package/analyzer-template/packages/analyze/src/lib/files/analyzeRemixRoute.ts +4 -5
- package/analyzer-template/packages/analyze/src/lib/files/getImportedExports.ts +14 -12
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/enrichArrayTypesFromChildSignatures.ts +57 -13
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.ts +29 -0
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateDataStructure.ts +35 -4
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.ts +117 -9
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +199 -17
- package/analyzer-template/packages/analyze/src/lib/files/scenarios/propagateArrayItemSchemas.ts +474 -0
- package/analyzer-template/packages/analyze/src/lib/files/setImportedExports.ts +2 -1
- package/analyzer-template/packages/analyze/src/lib/utils/getFileByPath.ts +19 -0
- package/analyzer-template/packages/aws/package.json +1 -1
- package/analyzer-template/packages/github/dist/types/src/types/ScenariosDataStructure.d.ts +5 -5
- package/analyzer-template/packages/github/dist/types/src/types/ScenariosDataStructure.d.ts.map +1 -1
- package/analyzer-template/packages/github/dist/types/src/types/ScopeAnalysis.d.ts +6 -1
- package/analyzer-template/packages/github/dist/types/src/types/ScopeAnalysis.d.ts.map +1 -1
- package/analyzer-template/packages/github/package.json +1 -1
- package/analyzer-template/packages/types/src/types/ScenariosDataStructure.ts +6 -5
- package/analyzer-template/packages/types/src/types/ScopeAnalysis.ts +6 -1
- package/analyzer-template/packages/utils/dist/types/src/types/ScenariosDataStructure.d.ts +5 -5
- package/analyzer-template/packages/utils/dist/types/src/types/ScenariosDataStructure.d.ts.map +1 -1
- package/analyzer-template/packages/utils/dist/types/src/types/ScopeAnalysis.d.ts +6 -1
- package/analyzer-template/packages/utils/dist/types/src/types/ScopeAnalysis.d.ts.map +1 -1
- package/analyzer-template/project/constructMockCode.ts +54 -9
- package/analyzer-template/project/writeMockDataTsx.ts +73 -2
- package/background/src/lib/virtualized/project/constructMockCode.js +45 -3
- package/background/src/lib/virtualized/project/constructMockCode.js.map +1 -1
- package/background/src/lib/virtualized/project/writeMockDataTsx.js +71 -2
- package/background/src/lib/virtualized/project/writeMockDataTsx.js.map +1 -1
- package/codeyam-cli/scripts/apply-setup.js +146 -0
- package/codeyam-cli/scripts/apply-setup.js.map +1 -1
- package/codeyam-cli/src/commands/debug.js +7 -5
- package/codeyam-cli/src/commands/debug.js.map +1 -1
- package/codeyam-cli/src/utils/install-skills.js +22 -0
- package/codeyam-cli/src/utils/install-skills.js.map +1 -1
- package/codeyam-cli/src/utils/reviewedRules.js +92 -0
- package/codeyam-cli/src/utils/reviewedRules.js.map +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/globals-CX9f-5xM.css +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/{manifest-7522edd4.js → manifest-bba56ec1.js} +1 -1
- package/codeyam-cli/src/webserver/build/client/assets/memory-DuTFSyJ2.js +92 -0
- package/codeyam-cli/src/webserver/build/client/assets/{root-eVAaavTS.js → root-DTfSQARG.js} +6 -6
- package/codeyam-cli/src/webserver/build/server/assets/{index-DVzYx8PN.js → index-TD1f-DHV.js} +1 -1
- package/codeyam-cli/src/webserver/build/server/assets/server-build-BQ-1XyEa.js +258 -0
- package/codeyam-cli/src/webserver/build/server/index.js +1 -1
- package/codeyam-cli/src/webserver/build-info.json +5 -5
- package/codeyam-cli/templates/codeyam:memory.md +174 -233
- package/codeyam-cli/templates/codeyam:new-rule.md +41 -2
- package/codeyam-cli/templates/rule-reflection-hook.py +161 -0
- package/codeyam-cli/templates/rules-instructions.md +126 -0
- package/package.json +1 -1
- package/packages/ai/index.js +2 -1
- package/packages/ai/index.js.map +1 -1
- package/packages/ai/src/lib/analyzeScope.js +29 -12
- package/packages/ai/src/lib/analyzeScope.js.map +1 -1
- package/packages/ai/src/lib/astScopes/astScopeAnalyzer.js +54 -8
- package/packages/ai/src/lib/astScopes/astScopeAnalyzer.js.map +1 -1
- package/packages/ai/src/lib/astScopes/processExpression.js +239 -43
- package/packages/ai/src/lib/astScopes/processExpression.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js +503 -165
- package/packages/ai/src/lib/dataStructure/ScopeDataStructure.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/helpers/BatchSchemaProcessor.js +13 -3
- package/packages/ai/src/lib/dataStructure/helpers/BatchSchemaProcessor.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/helpers/ScopeTreeManager.js +6 -4
- package/packages/ai/src/lib/dataStructure/helpers/ScopeTreeManager.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js +22 -1
- package/packages/ai/src/lib/dataStructure/helpers/cleanKnownObjectFunctions.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/helpers/cleanNonObjectFunctions.js +34 -9
- package/packages/ai/src/lib/dataStructure/helpers/cleanNonObjectFunctions.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/helpers/convertTypeAnnotationsToValues.js +159 -0
- package/packages/ai/src/lib/dataStructure/helpers/convertTypeAnnotationsToValues.js.map +1 -0
- package/packages/ai/src/lib/dataStructure/helpers/deduplicateFunctionSchemas.js +37 -20
- package/packages/ai/src/lib/dataStructure/helpers/deduplicateFunctionSchemas.js.map +1 -1
- package/packages/ai/src/lib/dataStructure/helpers/fillInSchemaGapsAndUnknowns.js +237 -73
- package/packages/ai/src/lib/dataStructure/helpers/fillInSchemaGapsAndUnknowns.js.map +1 -1
- package/packages/ai/src/lib/generateEntityScenarioData.js +195 -1
- package/packages/ai/src/lib/generateEntityScenarioData.js.map +1 -1
- package/packages/ai/src/lib/generateEntityScenarios.js +7 -1
- package/packages/ai/src/lib/generateEntityScenarios.js.map +1 -1
- package/packages/ai/src/lib/generateExecutionFlows.js +10 -2
- package/packages/ai/src/lib/generateExecutionFlows.js.map +1 -1
- package/packages/ai/src/lib/generateExecutionFlowsFromConditionals.js +209 -3
- package/packages/ai/src/lib/generateExecutionFlowsFromConditionals.js.map +1 -1
- package/packages/ai/src/lib/mergeStatements.js +70 -51
- package/packages/ai/src/lib/mergeStatements.js.map +1 -1
- package/packages/ai/src/lib/promptGenerators/gatherAttributesMap.js +10 -4
- package/packages/ai/src/lib/promptGenerators/gatherAttributesMap.js.map +1 -1
- package/packages/ai/src/lib/resolvePathToControllable.js +24 -14
- package/packages/ai/src/lib/resolvePathToControllable.js.map +1 -1
- package/packages/ai/src/lib/worker/SerializableDataStructure.js.map +1 -1
- package/packages/analyze/src/lib/FileAnalyzer.js +60 -36
- package/packages/analyze/src/lib/FileAnalyzer.js.map +1 -1
- package/packages/analyze/src/lib/ProjectAnalyzer.js +96 -26
- package/packages/analyze/src/lib/ProjectAnalyzer.js.map +1 -1
- package/packages/analyze/src/lib/asts/sourceFiles/getAllDeclaredEntityNodes.js +14 -0
- package/packages/analyze/src/lib/asts/sourceFiles/getAllDeclaredEntityNodes.js.map +1 -1
- package/packages/analyze/src/lib/asts/sourceFiles/getAllEntityNodes.js +14 -0
- package/packages/analyze/src/lib/asts/sourceFiles/getAllEntityNodes.js.map +1 -1
- package/packages/analyze/src/lib/asts/sourceFiles/getAllExports.js +6 -0
- package/packages/analyze/src/lib/asts/sourceFiles/getAllExports.js.map +1 -1
- package/packages/analyze/src/lib/asts/sourceFiles/getImportsAnalysis.js +6 -0
- package/packages/analyze/src/lib/asts/sourceFiles/getImportsAnalysis.js.map +1 -1
- package/packages/analyze/src/lib/asts/sourceFiles/getResolvedModule.js +39 -1
- package/packages/analyze/src/lib/asts/sourceFiles/getResolvedModule.js.map +1 -1
- package/packages/analyze/src/lib/asts/sourceFiles/getSourceFilesForAllImports.js +2 -1
- package/packages/analyze/src/lib/asts/sourceFiles/getSourceFilesForAllImports.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js +13 -5
- package/packages/analyze/src/lib/files/analyze/analyzeEntities/prepareDataStructures.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/analyzeEntities.js +14 -4
- package/packages/analyze/src/lib/files/analyze/analyzeEntities.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js +2 -1
- package/packages/analyze/src/lib/files/analyze/gatherEntityMap.js.map +1 -1
- package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js +0 -3
- package/packages/analyze/src/lib/files/analyze/validateDependencyAnalyses.js.map +1 -1
- package/packages/analyze/src/lib/files/analyzeRemixRoute.js +3 -2
- package/packages/analyze/src/lib/files/analyzeRemixRoute.js.map +1 -1
- package/packages/analyze/src/lib/files/getImportedExports.js +11 -7
- package/packages/analyze/src/lib/files/getImportedExports.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/enrichArrayTypesFromChildSignatures.js +52 -10
- package/packages/analyze/src/lib/files/scenarios/enrichArrayTypesFromChildSignatures.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js +25 -8
- package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js +34 -4
- package/packages/analyze/src/lib/files/scenarios/generateDataStructure.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.js +56 -8
- package/packages/analyze/src/lib/files/scenarios/generateExecutionFlows.js.map +1 -1
- package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +168 -9
- package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
- package/packages/analyze/src/lib/files/setImportedExports.js +2 -1
- package/packages/analyze/src/lib/files/setImportedExports.js.map +1 -1
- package/packages/analyze/src/lib/utils/getFileByPath.js +12 -0
- package/packages/analyze/src/lib/utils/getFileByPath.js.map +1 -0
- package/codeyam-cli/src/webserver/build/client/assets/globals-D3yhhV8x.css +0 -1
- package/codeyam-cli/src/webserver/build/client/assets/memory-yxFcrxBX.js +0 -92
- package/codeyam-cli/src/webserver/build/server/assets/server-build-4Cr0uToj.js +0 -257
|
@@ -25,6 +25,111 @@ import {
|
|
|
25
25
|
} from './conditionalEffectsExtractor';
|
|
26
26
|
import { detectArrayDerivedPattern } from './arrayDerivationDetector';
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Checks if a JSX element has props that reference variables from the parent scope.
|
|
30
|
+
* This is used to detect unconditionally-rendered children that should have their
|
|
31
|
+
* execution flows merged into the parent.
|
|
32
|
+
*
|
|
33
|
+
* We want to track children where the parent controls data that affects the child's
|
|
34
|
+
* conditional rendering. Static props (like title="Dashboard") don't need tracking
|
|
35
|
+
* because they don't create variable execution flows.
|
|
36
|
+
*
|
|
37
|
+
* Examples:
|
|
38
|
+
* - <WorkoutsView workouts={workouts} /> → true (workouts is a variable)
|
|
39
|
+
* - <ItemList items={items} count={count} /> → true (items, count are variables)
|
|
40
|
+
* - <Header title="Dashboard" /> → false (static string)
|
|
41
|
+
* - <Footer /> → false (no props)
|
|
42
|
+
* - <Button onClick={handleClick} /> → false (only callback, no data props)
|
|
43
|
+
*
|
|
44
|
+
* @returns true if the component has at least one prop that references a variable
|
|
45
|
+
* (excluding callbacks which typically start with 'on' or 'handle')
|
|
46
|
+
*/
|
|
47
|
+
function hasDataPropsFromParent(
|
|
48
|
+
node: ts.JsxElement | ts.JsxSelfClosingElement,
|
|
49
|
+
componentName: string,
|
|
50
|
+
): { hasDataProps: boolean; dataProps: string[] } {
|
|
51
|
+
const attributes = ts.isJsxElement(node)
|
|
52
|
+
? node.openingElement.attributes.properties
|
|
53
|
+
: node.attributes.properties;
|
|
54
|
+
|
|
55
|
+
const dataProps: string[] = [];
|
|
56
|
+
|
|
57
|
+
for (const attr of attributes) {
|
|
58
|
+
// Spread attributes always reference parent data: {...props}
|
|
59
|
+
if (ts.isJsxSpreadAttribute(attr)) {
|
|
60
|
+
const spreadText = attr.expression?.getText() || '...spread';
|
|
61
|
+
dataProps.push(`{...${spreadText}}`);
|
|
62
|
+
console.log(
|
|
63
|
+
`[UnconditionalChild] ${componentName}: Found spread attribute {${spreadText}}`,
|
|
64
|
+
);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (ts.isJsxAttribute(attr)) {
|
|
69
|
+
const propName = attr.name.getText();
|
|
70
|
+
|
|
71
|
+
// Skip callback props - they don't create data-driven execution flows
|
|
72
|
+
// Callbacks typically start with 'on' (onClick, onChange) or 'handle' (handleSubmit)
|
|
73
|
+
if (
|
|
74
|
+
propName.startsWith('on') ||
|
|
75
|
+
propName.startsWith('handle') ||
|
|
76
|
+
propName === 'ref'
|
|
77
|
+
) {
|
|
78
|
+
console.log(
|
|
79
|
+
`[UnconditionalChild] ${componentName}: Skipping callback prop '${propName}'`,
|
|
80
|
+
);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Check if the prop value is a JSX expression (references a variable)
|
|
85
|
+
// vs a string literal which is static
|
|
86
|
+
if (attr.initializer) {
|
|
87
|
+
if (ts.isJsxExpression(attr.initializer)) {
|
|
88
|
+
// JSX expression like prop={value} - this references a variable
|
|
89
|
+
// Could be a simple identifier, property access, or more complex expression
|
|
90
|
+
const expression = attr.initializer.expression;
|
|
91
|
+
if (expression) {
|
|
92
|
+
// Skip if it's just a function/arrow function (callback)
|
|
93
|
+
if (
|
|
94
|
+
ts.isArrowFunction(expression) ||
|
|
95
|
+
ts.isFunctionExpression(expression)
|
|
96
|
+
) {
|
|
97
|
+
console.log(
|
|
98
|
+
`[UnconditionalChild] ${componentName}: Skipping inline callback prop '${propName}'`,
|
|
99
|
+
);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
// This is a data prop that references parent state/props
|
|
103
|
+
const exprText = expression.getText();
|
|
104
|
+
dataProps.push(`${propName}={${exprText}}`);
|
|
105
|
+
console.log(
|
|
106
|
+
`[UnconditionalChild] ${componentName}: Found data prop '${propName}' = {${exprText}}`,
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
// String literals like prop="value" are static
|
|
111
|
+
console.log(
|
|
112
|
+
`[UnconditionalChild] ${componentName}: Skipping static prop '${propName}'`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const hasDataProps = dataProps.length > 0;
|
|
120
|
+
if (hasDataProps) {
|
|
121
|
+
console.log(
|
|
122
|
+
`[UnconditionalChild] ${componentName}: Has ${dataProps.length} data props: [${dataProps.join(', ')}]`,
|
|
123
|
+
);
|
|
124
|
+
} else {
|
|
125
|
+
console.log(
|
|
126
|
+
`[UnconditionalChild] ${componentName}: No data props found, will NOT track`,
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return { hasDataProps, dataProps };
|
|
131
|
+
}
|
|
132
|
+
|
|
28
133
|
/**
|
|
29
134
|
* Extracts the component name from a JSX element.
|
|
30
135
|
* Returns null for intrinsic elements (div, span, etc.) since we only care about
|
|
@@ -921,18 +1026,50 @@ function extractConditionalsFromJsx(
|
|
|
921
1026
|
// Recursively process nested JSX elements - Fix 32: pass parent conditions
|
|
922
1027
|
else if (ts.isJsxElement(child)) {
|
|
923
1028
|
// Check if this is a user-defined component (vs intrinsic element like div)
|
|
924
|
-
// If it's a component AND there are parent conditions, record the gating conditions
|
|
925
1029
|
const componentName = getComponentNameFromJsx(child);
|
|
926
|
-
if (componentName
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
1030
|
+
if (componentName) {
|
|
1031
|
+
if (parentConditions.length > 0) {
|
|
1032
|
+
// If there are parent conditions, record them as gating conditions
|
|
1033
|
+
console.log(
|
|
1034
|
+
`[ChildBoundary] ${componentName}: Conditionally rendered with ${parentConditions.length} gating conditions`,
|
|
1035
|
+
);
|
|
1036
|
+
for (const condition of parentConditions) {
|
|
1037
|
+
console.log(
|
|
1038
|
+
`[ChildBoundary] ${componentName}: Adding gating condition path='${condition.path}' isNegated=${condition.isNegated}`,
|
|
1039
|
+
);
|
|
1040
|
+
context.addChildBoundaryGatingCondition(componentName, {
|
|
1041
|
+
path: condition.path,
|
|
1042
|
+
conditionType: 'truthiness',
|
|
1043
|
+
location: 'ternary',
|
|
1044
|
+
sourceLocation: condition.sourceLocation,
|
|
1045
|
+
controlsJsxRendering: true,
|
|
1046
|
+
isNegated: condition.isNegated,
|
|
1047
|
+
});
|
|
1048
|
+
}
|
|
1049
|
+
} else {
|
|
1050
|
+
// No parent conditions - check if it has data props for unconditional tracking
|
|
1051
|
+
console.log(
|
|
1052
|
+
`[ChildBoundary] ${componentName}: Checking for unconditional rendering with data props...`,
|
|
1053
|
+
);
|
|
1054
|
+
const { hasDataProps, dataProps } = hasDataPropsFromParent(
|
|
1055
|
+
child,
|
|
1056
|
+
componentName,
|
|
1057
|
+
);
|
|
1058
|
+
if (hasDataProps) {
|
|
1059
|
+
// Fix: Track unconditionally-rendered children that receive data props
|
|
1060
|
+
// These need to be tracked for flow merging even without gating conditions
|
|
1061
|
+
// Example: <WorkoutsView workouts={workouts} /> - parent controls workouts data
|
|
1062
|
+
console.log(
|
|
1063
|
+
`[ChildBoundary] ${componentName}: TRACKING as unconditionally-rendered with data props: [${dataProps.join(', ')}]`,
|
|
1064
|
+
);
|
|
1065
|
+
context.addChildBoundaryGatingCondition(componentName, {
|
|
1066
|
+
path: '__unconditional__',
|
|
1067
|
+
conditionType: 'truthiness',
|
|
1068
|
+
location: 'unconditional',
|
|
1069
|
+
controlsJsxRendering: true,
|
|
1070
|
+
isNegated: false,
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
936
1073
|
}
|
|
937
1074
|
}
|
|
938
1075
|
extractConditionalsFromJsx(child, context, parentConditions);
|
|
@@ -940,18 +1077,48 @@ function extractConditionalsFromJsx(
|
|
|
940
1077
|
// Handle self-closing JSX elements (e.g., <ScenarioViewer />)
|
|
941
1078
|
else if (ts.isJsxSelfClosingElement(child)) {
|
|
942
1079
|
// Check if this is a user-defined component (vs intrinsic element like div)
|
|
943
|
-
// If it's a component AND there are parent conditions, record the gating conditions
|
|
944
1080
|
const componentName = getComponentNameFromJsx(child);
|
|
945
|
-
if (componentName
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1081
|
+
if (componentName) {
|
|
1082
|
+
if (parentConditions.length > 0) {
|
|
1083
|
+
// If there are parent conditions, record them as gating conditions
|
|
1084
|
+
console.log(
|
|
1085
|
+
`[ChildBoundary] ${componentName}: Conditionally rendered (self-closing) with ${parentConditions.length} gating conditions`,
|
|
1086
|
+
);
|
|
1087
|
+
for (const condition of parentConditions) {
|
|
1088
|
+
console.log(
|
|
1089
|
+
`[ChildBoundary] ${componentName}: Adding gating condition path='${condition.path}' isNegated=${condition.isNegated}`,
|
|
1090
|
+
);
|
|
1091
|
+
context.addChildBoundaryGatingCondition(componentName, {
|
|
1092
|
+
path: condition.path,
|
|
1093
|
+
conditionType: 'truthiness',
|
|
1094
|
+
location: 'ternary',
|
|
1095
|
+
sourceLocation: condition.sourceLocation,
|
|
1096
|
+
controlsJsxRendering: true,
|
|
1097
|
+
isNegated: condition.isNegated,
|
|
1098
|
+
});
|
|
1099
|
+
}
|
|
1100
|
+
} else {
|
|
1101
|
+
// No parent conditions - check if it has data props for unconditional tracking
|
|
1102
|
+
console.log(
|
|
1103
|
+
`[ChildBoundary] ${componentName}: Checking for unconditional rendering (self-closing) with data props...`,
|
|
1104
|
+
);
|
|
1105
|
+
const { hasDataProps, dataProps } = hasDataPropsFromParent(
|
|
1106
|
+
child,
|
|
1107
|
+
componentName,
|
|
1108
|
+
);
|
|
1109
|
+
if (hasDataProps) {
|
|
1110
|
+
// Fix: Track unconditionally-rendered children that receive data props
|
|
1111
|
+
console.log(
|
|
1112
|
+
`[ChildBoundary] ${componentName}: TRACKING as unconditionally-rendered (self-closing) with data props: [${dataProps.join(', ')}]`,
|
|
1113
|
+
);
|
|
1114
|
+
context.addChildBoundaryGatingCondition(componentName, {
|
|
1115
|
+
path: '__unconditional__',
|
|
1116
|
+
conditionType: 'truthiness',
|
|
1117
|
+
location: 'unconditional',
|
|
1118
|
+
controlsJsxRendering: true,
|
|
1119
|
+
isNegated: false,
|
|
1120
|
+
});
|
|
1121
|
+
}
|
|
955
1122
|
}
|
|
956
1123
|
}
|
|
957
1124
|
// Self-closing elements have no children, so no recursion needed
|
|
@@ -1449,7 +1616,7 @@ export function processExpression({
|
|
|
1449
1616
|
const structure = context.getStructure();
|
|
1450
1617
|
|
|
1451
1618
|
// Propagate existing equivalencies for sub-properties
|
|
1452
|
-
for (const [key,
|
|
1619
|
+
for (const [key, rawValue] of Object.entries(equivalentVariables)) {
|
|
1453
1620
|
// Check if this equivalency is for a sub-property of the identifier
|
|
1454
1621
|
// e.g., completeDataStructure['Function Arguments'] or completeDataStructure.foo
|
|
1455
1622
|
if (
|
|
@@ -1460,8 +1627,14 @@ export function processExpression({
|
|
|
1460
1627
|
const newTargetPath = StructuredPath.fromBase(
|
|
1461
1628
|
targetPath.toString() + subPath,
|
|
1462
1629
|
);
|
|
1463
|
-
|
|
1464
|
-
|
|
1630
|
+
// Handle both string and string[] values
|
|
1631
|
+
const values = Array.isArray(rawValue) ? rawValue : [rawValue];
|
|
1632
|
+
for (const value of values) {
|
|
1633
|
+
if (typeof value === 'string') {
|
|
1634
|
+
const valuePath = StructuredPath.fromBase(value);
|
|
1635
|
+
context.addEquivalence(newTargetPath, valuePath);
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1465
1638
|
}
|
|
1466
1639
|
}
|
|
1467
1640
|
|
|
@@ -1945,23 +2118,47 @@ export function processExpression({
|
|
|
1945
2118
|
// - items[] (from the find result)
|
|
1946
2119
|
// - null (from the fallback)
|
|
1947
2120
|
if (targetPath) {
|
|
1948
|
-
//
|
|
2121
|
+
// Get paths for both sides
|
|
2122
|
+
const leftPath = StructuredPath.fromNode(
|
|
2123
|
+
unwrappedNode.left,
|
|
2124
|
+
context.sourceFile,
|
|
2125
|
+
);
|
|
2126
|
+
const rightPath = StructuredPath.fromNode(
|
|
2127
|
+
unwrappedNode.right,
|
|
2128
|
+
context.sourceFile,
|
|
2129
|
+
);
|
|
2130
|
+
|
|
2131
|
+
// Collect all valid paths
|
|
2132
|
+
const allPaths: StructuredPath[] = [];
|
|
2133
|
+
if (leftPath) allPaths.push(leftPath);
|
|
2134
|
+
if (rightPath) allPaths.push(rightPath);
|
|
2135
|
+
|
|
2136
|
+
// Add multiple equivalencies to track both sources
|
|
2137
|
+
if (allPaths.length > 0) {
|
|
2138
|
+
context.addMultipleEquivalencies(targetPath, allPaths);
|
|
2139
|
+
}
|
|
2140
|
+
|
|
2141
|
+
// Process both sides to capture their internal structures
|
|
1949
2142
|
processExpression({
|
|
1950
2143
|
node: unwrappedNode.left,
|
|
1951
2144
|
context,
|
|
1952
|
-
targetPath,
|
|
1953
2145
|
});
|
|
1954
|
-
// Process right side recursively for completeness
|
|
1955
2146
|
processExpression({
|
|
1956
2147
|
node: unwrappedNode.right,
|
|
1957
2148
|
context,
|
|
1958
|
-
targetPath,
|
|
1959
2149
|
});
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
)
|
|
2150
|
+
|
|
2151
|
+
// Register the type for the target path
|
|
2152
|
+
const leftType = context.inferTypeFromNode(unwrappedNode.left);
|
|
2153
|
+
const rightType = context.inferTypeFromNode(unwrappedNode.right);
|
|
2154
|
+
const orResultType = isDefinedType(leftType)
|
|
2155
|
+
? leftType
|
|
2156
|
+
: rightType || 'unknown';
|
|
2157
|
+
context.addType(targetPath, orResultType);
|
|
2158
|
+
|
|
2159
|
+
// Return early - we've already handled equivalencies with addMultipleEquivalencies
|
|
2160
|
+
// Don't fall through to the generic addEquivalence call below
|
|
2161
|
+
return true;
|
|
1965
2162
|
}
|
|
1966
2163
|
// Note: When there's no targetPath, we don't recursively process
|
|
1967
2164
|
// because || is often used in boolean contexts where the full expression matters
|
|
@@ -2071,24 +2268,56 @@ export function processExpression({
|
|
|
2071
2268
|
|
|
2072
2269
|
// Get the source expression path (e.g., the object for obj.method())
|
|
2073
2270
|
const sourceExpr = unwrappedNode.expression.expression;
|
|
2074
|
-
const
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2271
|
+
const unwrappedSourceExpr = unwrapExpression(sourceExpr);
|
|
2272
|
+
|
|
2273
|
+
// When the source is a ternary expression like (cond ? arr : arr.slice()),
|
|
2274
|
+
// apply method semantics to BOTH branches directly. The ternary itself isn't
|
|
2275
|
+
// a variable - it's just a choice between two paths that both flow to the result.
|
|
2276
|
+
if (ts.isConditionalExpression(unwrappedSourceExpr)) {
|
|
2277
|
+
const branches = [
|
|
2278
|
+
unwrappedSourceExpr.whenTrue,
|
|
2279
|
+
unwrappedSourceExpr.whenFalse,
|
|
2280
|
+
];
|
|
2078
2281
|
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2282
|
+
for (const branch of branches) {
|
|
2283
|
+
const branchPath = StructuredPath.fromNode(
|
|
2284
|
+
branch,
|
|
2285
|
+
context.sourceFile,
|
|
2286
|
+
);
|
|
2287
|
+
if (branchPath) {
|
|
2288
|
+
const isArraySemantics = semantics instanceof ArrayPushSemantics;
|
|
2289
|
+
const shouldApply =
|
|
2290
|
+
!isArraySemantics ||
|
|
2291
|
+
isLikelyArrayType(branch, context.typeChecker);
|
|
2292
|
+
|
|
2293
|
+
if (shouldApply) {
|
|
2294
|
+
semantics.addEquivalences(callPath, branchPath, context);
|
|
2295
|
+
returnType = semantics.getReturnType();
|
|
2296
|
+
handledBySemantics = true;
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2300
|
+
} else {
|
|
2301
|
+
// Regular (non-ternary) source expression
|
|
2302
|
+
const sourcePath = StructuredPath.fromNode(
|
|
2303
|
+
sourceExpr,
|
|
2304
|
+
context.sourceFile,
|
|
2305
|
+
);
|
|
2086
2306
|
|
|
2087
|
-
if (
|
|
2088
|
-
//
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2307
|
+
if (sourcePath) {
|
|
2308
|
+
// For array-specific semantics (like push), verify the source is actually an array
|
|
2309
|
+
// This prevents router.push() from being mistakenly treated as Array.push()
|
|
2310
|
+
const isArraySemantics = semantics instanceof ArrayPushSemantics;
|
|
2311
|
+
const shouldApply =
|
|
2312
|
+
!isArraySemantics ||
|
|
2313
|
+
isLikelyArrayType(sourceExpr, context.typeChecker);
|
|
2314
|
+
|
|
2315
|
+
if (shouldApply) {
|
|
2316
|
+
// Apply method semantics
|
|
2317
|
+
semantics.addEquivalences(callPath, sourcePath, context);
|
|
2318
|
+
returnType = semantics.getReturnType();
|
|
2319
|
+
handledBySemantics = true;
|
|
2320
|
+
}
|
|
2092
2321
|
}
|
|
2093
2322
|
}
|
|
2094
2323
|
}
|
|
@@ -2646,6 +2875,35 @@ export function processExpression({
|
|
|
2646
2875
|
|
|
2647
2876
|
// Handle Arrow Functions: (p) => p.prop, (a, b) => { ... }
|
|
2648
2877
|
if (ts.isArrowFunction(unwrappedNode)) {
|
|
2878
|
+
// If this arrow function is a child boundary (e.g., a .map() callback),
|
|
2879
|
+
// don't process its parameters here - they will be processed when the
|
|
2880
|
+
// child scope is analyzed separately. This prevents parameter variables
|
|
2881
|
+
// from leaking into the parent scope's equivalencies.
|
|
2882
|
+
// Check if this arrow function is a child boundary (i.e., should be processed
|
|
2883
|
+
// as a separate child scope, not here in the parent scope).
|
|
2884
|
+
//
|
|
2885
|
+
// We use two checks because childBoundary positions can be unreliable:
|
|
2886
|
+
// 1. Position-based check (standard isChildBoundary)
|
|
2887
|
+
// 2. Text-based check: if the arrow function text doesn't appear in the
|
|
2888
|
+
// statement text, it was replaced with a cyScope placeholder
|
|
2889
|
+
const isChildBoundary = context.isChildBoundary(unwrappedNode);
|
|
2890
|
+
|
|
2891
|
+
// Text-based child scope detection for when positions are unreliable
|
|
2892
|
+
const arrowFnText = unwrappedNode.getText(context.sourceFile);
|
|
2893
|
+
const firstLine = arrowFnText.split('\n')[0].trim();
|
|
2894
|
+
const searchText = firstLine.substring(0, Math.min(20, firstLine.length));
|
|
2895
|
+
const isInStatementText = context.statementInfo.text.includes(searchText);
|
|
2896
|
+
const isChildScope = !isInStatementText && arrowFnText.length > 10;
|
|
2897
|
+
|
|
2898
|
+
if (isChildBoundary || isChildScope) {
|
|
2899
|
+
// The method semantics (e.g., ArrayMapSemantics) have already established
|
|
2900
|
+
// the necessary equivalences between the child scope placeholder and array elements
|
|
2901
|
+
if (targetPath) {
|
|
2902
|
+
context.addType(targetPath, 'function');
|
|
2903
|
+
}
|
|
2904
|
+
return true;
|
|
2905
|
+
}
|
|
2906
|
+
|
|
2649
2907
|
// Create a path for the function
|
|
2650
2908
|
const functionPath = StructuredPath.empty();
|
|
2651
2909
|
|
|
@@ -287,8 +287,9 @@ export interface AstScopeAnalysisResult {
|
|
|
287
287
|
// Variable structure map (variable path -> type)
|
|
288
288
|
structure: Record<string, string>;
|
|
289
289
|
|
|
290
|
-
// Variable equivalence map (variable path -> equivalent variable path)
|
|
291
|
-
|
|
290
|
+
// Variable equivalence map (variable path -> equivalent variable path(s))
|
|
291
|
+
// Supports multiple equivalencies per key for OR expressions (e.g., x = a || b)
|
|
292
|
+
equivalentVariables: Record<string, string | string[]>;
|
|
292
293
|
|
|
293
294
|
// Environment variables accessed in this scope
|
|
294
295
|
environmentVariables: string[];
|
|
@@ -348,8 +349,9 @@ export interface ConditionalUsage {
|
|
|
348
349
|
|
|
349
350
|
/**
|
|
350
351
|
* Where this conditional usage occurs
|
|
352
|
+
* 'unconditional' is used for children that are always rendered but receive data props
|
|
351
353
|
*/
|
|
352
|
-
location: 'if' | 'ternary' | 'logical-and' | 'switch';
|
|
354
|
+
location: 'if' | 'ternary' | 'logical-and' | 'switch' | 'unconditional';
|
|
353
355
|
|
|
354
356
|
/**
|
|
355
357
|
* Source location information for this conditional usage
|
|
@@ -441,7 +443,7 @@ export interface CompoundConditional {
|
|
|
441
443
|
orGroupId?: string;
|
|
442
444
|
}[];
|
|
443
445
|
/** Where this compound conditional occurs */
|
|
444
|
-
location: 'if' | 'ternary' | 'logical-and' | 'switch';
|
|
446
|
+
location: 'if' | 'ternary' | 'logical-and' | 'switch' | 'unconditional';
|
|
445
447
|
/** Source location for the entire compound expression */
|
|
446
448
|
sourceLocation: {
|
|
447
449
|
lineNumber: number;
|
|
@@ -509,6 +511,13 @@ export interface AnalysisContext {
|
|
|
509
511
|
// Add an equivalence relationship between two variable paths
|
|
510
512
|
addEquivalence(leftSide: StructuredPath, rightSide: StructuredPath): void;
|
|
511
513
|
|
|
514
|
+
// Add multiple equivalence relationships for a single variable path.
|
|
515
|
+
// Used for OR expressions like `x = a || b` where x is equivalent to both a and b.
|
|
516
|
+
addMultipleEquivalencies(
|
|
517
|
+
leftSide: StructuredPath,
|
|
518
|
+
rightSides: StructuredPath[],
|
|
519
|
+
): void;
|
|
520
|
+
|
|
512
521
|
// Add a type for a variable path
|
|
513
522
|
addType(path: StructuredPath, type: string): void;
|
|
514
523
|
|
|
@@ -555,8 +564,8 @@ export interface AnalysisContext {
|
|
|
555
564
|
// Get the built-up structure
|
|
556
565
|
getStructure(): Record<string, string>;
|
|
557
566
|
|
|
558
|
-
// Get the built-up equivalence map
|
|
559
|
-
getEquivalentVariables(): Record<string, string>;
|
|
567
|
+
// Get the built-up equivalence map (supports multiple values for OR expressions)
|
|
568
|
+
getEquivalentVariables(): Record<string, string | string[]>;
|
|
560
569
|
|
|
561
570
|
// Utilties for child boundaries (need to be skipped while capturing equivalencies)
|
|
562
571
|
getChildBoundary(
|