@shapeshift-labs/frontier-lang-compiler 0.2.162 → 0.2.164

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 (24) hide show
  1. package/dist/internal/index-impl/moduleHostResourceImportMetadata.js +47 -0
  2. package/dist/internal/index-impl/projectSymbolGraphCompilerAdvancedTypeMetadata.js +84 -12
  3. package/dist/internal/index-impl/projectSymbolGraphCompilerFacts.js +1 -1
  4. package/dist/internal/index-impl/projectSymbolGraphCssModules.js +48 -4
  5. package/dist/internal/index-impl/projectSymbolGraphJsxPropRecordFields.js +91 -0
  6. package/dist/internal/index-impl/projectSymbolGraphJsxPropValues.js +26 -135
  7. package/dist/internal/index-impl/projectSymbolGraphJsxRecords.js +4 -31
  8. package/dist/internal/index-impl/projectSymbolGraphJsxRenderReturnCollectionHelpers.js +201 -0
  9. package/dist/internal/index-impl/projectSymbolGraphJsxRenderReturnCollections.js +210 -0
  10. package/dist/internal/index-impl/projectSymbolGraphJsxRenderReturns.js +12 -5
  11. package/dist/internal/index-impl/projectSymbolGraphJsxSpreadPropValues.js +196 -0
  12. package/dist/internal/index-impl/projectSymbolGraphJsxStaticLiterals.js +207 -0
  13. package/dist/internal/index-impl/projectSymbolGraphModuleResolution.js +12 -14
  14. package/dist/internal/index-impl/projectSymbolGraphPackageConditions.js +22 -17
  15. package/dist/internal/index-impl/syntaxModuleDeclarationEntries.js +27 -1
  16. package/dist/js-ts-safe-project-merge-html-css-matrix.js +7 -2
  17. package/dist/js-ts-safe-project-merge-html-css-summary.js +90 -8
  18. package/dist/js-ts-safe-project-merge-html-css.js +137 -3
  19. package/dist/js-ts-safe-project-merge-jsx-graph-conflict-details.js +9 -0
  20. package/dist/js-ts-safe-project-merge-missing-evidence.js +12 -12
  21. package/dist/js-ts-safe-project-merge-source-text-candidate.js +68 -0
  22. package/dist/js-ts-safe-project-merge-summary.js +8 -11
  23. package/dist/js-ts-safe-project-merge.js +3 -0
  24. package/package.json +1 -1
@@ -0,0 +1,201 @@
1
+ function staticMapCall(expressionText) {
2
+ const value = normalizedReturnExpression(expressionText);
3
+ const target = /^([A-Za-z_$][\w$]*)\s*\.\s*map\s*\(/.exec(value);
4
+ if (!target) return undefined;
5
+ const argsStart = target[0].length - 1;
6
+ const argsEnd = matchingClose(value, argsStart, '(', ')');
7
+ if (argsEnd !== value.length - 1) return undefined;
8
+ return {
9
+ arrayName: target[1],
10
+ callbackText: normalizedText(value.slice(argsStart + 1, argsEnd))
11
+ };
12
+ }
13
+
14
+ function staticMapCallback(callbackText) {
15
+ const value = normalizedText(callbackText);
16
+ const match = /^(?:\(\s*([A-Za-z_$][\w$]*)(?:\s*,\s*([A-Za-z_$][\w$]*))?\s*\)|([A-Za-z_$][\w$]*))\s*=>\s*([\s\S]*)$/.exec(value);
17
+ if (!match) return undefined;
18
+ const bodyExpressionText = normalizedReturnExpression(match[4]);
19
+ if (bodyExpressionText.startsWith('{')) return undefined;
20
+ return {
21
+ callbackText: value,
22
+ parameterName: match[1] ?? match[3],
23
+ indexParameterName: match[2],
24
+ bodyExpressionText
25
+ };
26
+ }
27
+
28
+ function constArrayLiteralBinding(sourceText, arrayName) {
29
+ const value = String(sourceText ?? '');
30
+ const declaration = new RegExp(`\\bconst\\s+${escapeRegExp(arrayName)}\\s*=\\s*\\[`, 'g').exec(value);
31
+ if (!declaration) return undefined;
32
+ const arrayStart = value.indexOf('[', declaration.index);
33
+ const arrayEnd = matchingClose(value, arrayStart, '[', ']');
34
+ if (arrayEnd < 0) return undefined;
35
+ return { arrayLiteralText: normalizedText(value.slice(arrayStart, arrayEnd + 1)) };
36
+ }
37
+
38
+ function staticMapSourceItemRecord(sourceItemExpressionText) {
39
+ const literal = staticLiteralValue(sourceItemExpressionText);
40
+ if (literal) return { kind: literal.kind, props: new Map() };
41
+ const props = staticObjectLiteralProps(sourceItemExpressionText);
42
+ return props ? { kind: 'object-literal', props } : undefined;
43
+ }
44
+
45
+ function staticObjectLiteralProps(sourceItemExpressionText) {
46
+ const value = normalizedReturnExpression(sourceItemExpressionText);
47
+ if (!value.startsWith('{') || !value.endsWith('}')) return undefined;
48
+ const parts = splitTopLevel(value.slice(1, -1), ',').filter((part) => normalizedText(part));
49
+ if (!parts.length) return undefined;
50
+ const props = new Map();
51
+ for (const part of parts) {
52
+ const match = /^\s*([A-Za-z_$][\w$]*)\s*:\s*([\s\S]*?)\s*$/.exec(part);
53
+ if (!match) return undefined;
54
+ const literal = staticLiteralValue(match[2]);
55
+ if (!literal) return undefined;
56
+ props.set(match[1], literal);
57
+ }
58
+ return props;
59
+ }
60
+
61
+ function staticLiteralValue(text) {
62
+ const value = normalizedText(text);
63
+ const string = /^"([^"]*)"|'([^']*)'$/.exec(value);
64
+ if (string) return { value: string[1] ?? string[2] ?? '', text: value, kind: 'string' };
65
+ if (/^-?\d+(?:\.\d+)?$/.test(value)) return { value: Number(value), text: value, kind: 'number' };
66
+ if (value === 'true' || value === 'false') return { value: value === 'true', text: value, kind: 'boolean' };
67
+ if (value === 'null') return { value: null, text: value, kind: 'null' };
68
+ return undefined;
69
+ }
70
+
71
+ function mapKeyResolution(keyEvidence, parameterName, sourceItemProps) {
72
+ if (!keyEvidence) return undefined;
73
+ if (keyEvidence.keyValue !== undefined) return { keyValue: keyEvidence.keyValue, keyStatic: true };
74
+ const prefix = `${parameterName}.`;
75
+ if (!keyEvidence.keyExpressionText?.startsWith(prefix)) return { keyStatic: false };
76
+ const keySourcePropName = keyEvidence.keyExpressionText.slice(prefix.length);
77
+ if (!/^[A-Za-z_$][\w$]*$/.test(keySourcePropName)) return { keyStatic: false };
78
+ const prop = sourceItemProps.get(keySourcePropName);
79
+ return prop ? { keyValue: prop.value, keySourcePropName, keyStatic: true } : { keySourcePropName, keyStatic: false };
80
+ }
81
+
82
+ function jsxKeyEvidence(expressionText) {
83
+ const openingTag = firstJsxOpeningTagText(expressionText);
84
+ if (!openingTag) return undefined;
85
+ const match = /\bkey\s*=\s*("[^"]*"|'[^']*'|\{[^}]*\})/.exec(openingTag);
86
+ if (!match) return undefined;
87
+ const rawValue = match[1].trim();
88
+ const quoted = /^"([^"]*)"|'([^']*)'$/.exec(rawValue);
89
+ const braced = /^\{\s*([\s\S]*?)\s*\}$/.exec(rawValue);
90
+ return compactRecord({
91
+ keyPropText: normalizedText(match[0]),
92
+ keyValue: quoted ? quoted[1] ?? quoted[2] ?? '' : undefined,
93
+ keyExpressionText: braced ? normalizedText(braced[1]) : undefined
94
+ });
95
+ }
96
+
97
+ function firstJsxOpeningTagText(expressionText) {
98
+ const value = normalizedReturnExpression(expressionText);
99
+ const start = value.indexOf('<');
100
+ if (start < 0 || value[start + 1] === '/' || value[start + 1] === '>') return undefined;
101
+ let quote;
102
+ let braceDepth = 0;
103
+ for (let index = start + 1; index < value.length; index += 1) {
104
+ const char = value[index];
105
+ if (quote) {
106
+ if (char === '\\') index += 1;
107
+ else if (char === quote) quote = undefined;
108
+ continue;
109
+ }
110
+ if (char === '"' || char === "'" || char === '`') quote = char;
111
+ else if (char === '{') braceDepth += 1;
112
+ else if (char === '}') braceDepth = Math.max(0, braceDepth - 1);
113
+ else if (char === '>' && braceDepth === 0) return value.slice(start, index + 1);
114
+ }
115
+ return undefined;
116
+ }
117
+
118
+ function directJsxChildren(text) {
119
+ const value = normalizedText(text);
120
+ const children = [];
121
+ const childPattern = /<([A-Za-z][\w.]*)\b[^>]*\/>|<([A-Za-z][\w.]*)\b[^>]*>[\s\S]*?<\/\2>/g;
122
+ for (const match of value.matchAll(childPattern)) children.push(normalizedText(match[0]));
123
+ return children;
124
+ }
125
+
126
+ function isJsxExpression(text) {
127
+ const value = normalizedReturnExpression(text);
128
+ return /^<[A-Za-z][\w.]*(?:\s|>|\/>)/.test(value) || /^React\s*\.\s*createElement\s*\(/.test(value);
129
+ }
130
+
131
+ function splitTopLevel(text, delimiter) {
132
+ const value = String(text ?? '');
133
+ const parts = [];
134
+ let quote;
135
+ let depth = 0;
136
+ let start = 0;
137
+ for (let index = 0; index < value.length; index += 1) {
138
+ const char = value[index];
139
+ if (quote) {
140
+ if (char === '\\') index += 1;
141
+ else if (char === quote) quote = undefined;
142
+ continue;
143
+ }
144
+ if (char === '"' || char === "'" || char === '`') quote = char;
145
+ else if (char === '(' || char === '[' || char === '{' || char === '<') depth += 1;
146
+ else if (char === ')' || char === ']' || char === '}') depth = Math.max(0, depth - 1);
147
+ else if (char === '>' && depth > 0) depth = Math.max(0, depth - 1);
148
+ else if (depth === 0 && value.slice(index, index + delimiter.length) === delimiter) {
149
+ parts.push(value.slice(start, index));
150
+ start = index + delimiter.length;
151
+ index += delimiter.length - 1;
152
+ }
153
+ }
154
+ parts.push(value.slice(start));
155
+ return parts;
156
+ }
157
+
158
+ function matchingClose(text, openIndex, openChar, closeChar) {
159
+ const value = String(text ?? '');
160
+ let quote;
161
+ let depth = 0;
162
+ for (let index = openIndex; index < value.length; index += 1) {
163
+ const char = value[index];
164
+ if (quote) {
165
+ if (char === '\\') index += 1;
166
+ else if (char === quote) quote = undefined;
167
+ continue;
168
+ }
169
+ if (char === '"' || char === "'" || char === '`') quote = char;
170
+ else if (char === openChar) depth += 1;
171
+ else if (char === closeChar) {
172
+ depth -= 1;
173
+ if (depth === 0) return index;
174
+ }
175
+ }
176
+ return -1;
177
+ }
178
+
179
+ function escapeRegExp(text) { return String(text ?? '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
180
+ function normalizedText(text) { return String(text ?? '').trim().replace(/\s+/g, ' '); }
181
+ function normalizedReturnExpression(text) {
182
+ const value = normalizedText(text);
183
+ const wrapped = /^\(([\s\S]*)\)$/.exec(value);
184
+ return wrapped ? normalizedText(wrapped[1]) : value;
185
+ }
186
+ function compactRecord(record) { return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined)); }
187
+
188
+ export {
189
+ compactRecord,
190
+ constArrayLiteralBinding,
191
+ directJsxChildren,
192
+ isJsxExpression,
193
+ jsxKeyEvidence,
194
+ mapKeyResolution,
195
+ normalizedReturnExpression,
196
+ normalizedText,
197
+ splitTopLevel,
198
+ staticMapCall,
199
+ staticMapCallback,
200
+ staticMapSourceItemRecord
201
+ };
@@ -0,0 +1,210 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+ import {
3
+ compactRecord,
4
+ constArrayLiteralBinding,
5
+ directJsxChildren,
6
+ isJsxExpression,
7
+ jsxKeyEvidence,
8
+ mapKeyResolution,
9
+ normalizedReturnExpression,
10
+ splitTopLevel,
11
+ staticMapCall,
12
+ staticMapCallback,
13
+ staticMapSourceItemRecord
14
+ } from './projectSymbolGraphJsxRenderReturnCollectionHelpers.js';
15
+
16
+ function jsxRenderReturnCollectionRecord(expressionText, sourceText) {
17
+ const value = normalizedReturnExpression(expressionText);
18
+ return arrayLiteralCollectionRecord(value)
19
+ ?? fragmentCollectionRecord(value)
20
+ ?? staticConstArrayMapCollectionRecord(value, sourceText);
21
+ }
22
+
23
+ function arrayLiteralCollectionRecord(expressionText) {
24
+ if (!expressionText.startsWith('[') || !expressionText.endsWith(']')) return undefined;
25
+ const itemExpressionTexts = splitTopLevel(expressionText.slice(1, -1), ',').map(normalizedReturnExpression).filter(Boolean);
26
+ if (!itemExpressionTexts.length || itemExpressionTexts.some((item) => !isJsxExpression(item))) return undefined;
27
+ return jsxCollectionRecord({
28
+ proofStatus: 'static-render-return-array-evidence',
29
+ reasonCode: 'jsx-render-return-array-static-evidence',
30
+ collectionKind: 'array-literal',
31
+ itemExpressionTexts,
32
+ itemRecords: collectionItemRecords('array-literal', itemExpressionTexts)
33
+ });
34
+ }
35
+
36
+ function fragmentCollectionRecord(expressionText) {
37
+ if (expressionText.startsWith('<>') && expressionText.endsWith('</>')) {
38
+ const itemExpressionTexts = directJsxChildren(expressionText.slice(2, -3));
39
+ if (!itemExpressionTexts.length) return undefined;
40
+ return jsxCollectionRecord({
41
+ proofStatus: 'static-render-return-fragment-evidence',
42
+ reasonCode: 'jsx-render-return-fragment-static-evidence',
43
+ collectionKind: 'fragment-shorthand',
44
+ itemExpressionTexts,
45
+ itemRecords: collectionItemRecords('fragment-shorthand', itemExpressionTexts)
46
+ });
47
+ }
48
+ const named = /^<((?:React\.)?Fragment)\b[^>]*>([\s\S]*)<\/\1>$/.exec(expressionText);
49
+ if (!named) return undefined;
50
+ const itemExpressionTexts = directJsxChildren(named[2]);
51
+ if (!itemExpressionTexts.length) return undefined;
52
+ const collectionKind = named[1] === 'React.Fragment' ? 'fragment-react' : 'fragment-named';
53
+ return jsxCollectionRecord({
54
+ proofStatus: 'static-render-return-fragment-evidence',
55
+ reasonCode: 'jsx-render-return-fragment-static-evidence',
56
+ collectionKind,
57
+ itemExpressionTexts,
58
+ itemRecords: collectionItemRecords(collectionKind, itemExpressionTexts)
59
+ });
60
+ }
61
+
62
+ function staticConstArrayMapCollectionRecord(expressionText, sourceText) {
63
+ const mapCall = staticMapCall(expressionText);
64
+ if (!mapCall) return undefined;
65
+ const arrayBinding = constArrayLiteralBinding(sourceText, mapCall.arrayName);
66
+ if (!arrayBinding) return undefined;
67
+ const callback = staticMapCallback(mapCall.callbackText);
68
+ if (!callback || !isJsxExpression(callback.bodyExpressionText)) return undefined;
69
+ const sourceItemExpressionTexts = splitTopLevel(arrayBinding.arrayLiteralText.slice(1, -1), ',').map(normalizedReturnExpression).filter(Boolean);
70
+ if (!sourceItemExpressionTexts.length) return undefined;
71
+ const sourceItemRecords = sourceItemExpressionTexts.map(staticMapSourceItemRecord);
72
+ if (sourceItemRecords.some((record) => !record)) return undefined;
73
+ const keyEvidence = jsxKeyEvidence(callback.bodyExpressionText);
74
+ const itemExpressionTexts = sourceItemExpressionTexts.map(() => callback.bodyExpressionText);
75
+ const itemRecords = sourceItemExpressionTexts.map((sourceItemExpressionText, index) => staticConstArrayMapItemRecord({
76
+ callback,
77
+ index,
78
+ keyEvidence,
79
+ sourceItem: sourceItemRecords[index],
80
+ sourceItemExpressionText
81
+ }));
82
+ return jsxCollectionRecord({
83
+ proofStatus: 'static-render-return-map-evidence',
84
+ reasonCode: 'jsx-render-return-static-const-array-map-evidence',
85
+ collectionKind: 'static-const-array-map',
86
+ claimScope: 'static-const-array-map-structure-only',
87
+ renderEquivalenceClaim: false,
88
+ runtimeEquivalenceClaim: false,
89
+ sourceArrayName: mapCall.arrayName,
90
+ sourceArrayItemCount: sourceItemExpressionTexts.length,
91
+ sourceItemExpressionTexts,
92
+ mapCallbackExpressionText: callback.callbackText,
93
+ mapParameterName: callback.parameterName,
94
+ mapIndexParameterName: callback.indexParameterName,
95
+ callbackExpressionText: callback.bodyExpressionText,
96
+ itemExpressionTexts,
97
+ itemRecords
98
+ });
99
+ }
100
+
101
+ function staticConstArrayMapItemRecord(input) {
102
+ const { callback, index, keyEvidence, sourceItem, sourceItemExpressionText } = input;
103
+ const keyResolution = mapKeyResolution(keyEvidence, callback.parameterName, sourceItem.props);
104
+ return compactRecord({
105
+ ordinal: index + 1,
106
+ proofStatus: 'static-render-return-map-item-evidence',
107
+ expressionText: callback.bodyExpressionText,
108
+ expressionHash: hashSemanticValue({ kind: 'frontier.lang.projectJsxRenderReturnMapItemExpression', text: callback.bodyExpressionText }),
109
+ sourceItemExpressionText,
110
+ sourceItemExpressionHash: hashSemanticValue({ kind: 'frontier.lang.projectJsxRenderReturnMapSourceItem', text: sourceItemExpressionText }),
111
+ sourceItemKind: sourceItem.kind,
112
+ keyPropText: keyEvidence?.keyPropText,
113
+ keyExpressionText: keyEvidence?.keyExpressionText,
114
+ keyValue: keyResolution?.keyValue,
115
+ keySourcePropName: keyResolution?.keySourcePropName,
116
+ keyStatic: keyResolution?.keyStatic,
117
+ signatureHash: hashSemanticValue({
118
+ kind: 'frontier.lang.projectJsxRenderReturnMapItem',
119
+ ordinal: index + 1,
120
+ expressionText: callback.bodyExpressionText,
121
+ sourceItemExpressionText,
122
+ sourceItemKind: sourceItem.kind,
123
+ keyPropText: keyEvidence?.keyPropText,
124
+ keyExpressionText: keyEvidence?.keyExpressionText,
125
+ keyValue: keyResolution?.keyValue,
126
+ keySourcePropName: keyResolution?.keySourcePropName
127
+ })
128
+ });
129
+ }
130
+
131
+ function jsxCollectionRecord(record) {
132
+ const itemRecords = record.itemRecords ?? [];
133
+ const keyedListRecord = keyedListRecordFor(record.collectionKind, itemRecords);
134
+ return compactRecord({
135
+ ...record,
136
+ itemCount: itemRecords.length,
137
+ keyedListRecord,
138
+ signatureHash: hashSemanticValue({
139
+ kind: 'frontier.lang.projectJsxRenderReturnCollection',
140
+ proofStatus: record.proofStatus,
141
+ collectionKind: record.collectionKind,
142
+ sourceArrayName: record.sourceArrayName,
143
+ sourceItemExpressionTexts: record.sourceItemExpressionTexts,
144
+ mapCallbackExpressionText: record.mapCallbackExpressionText,
145
+ itemExpressionTexts: record.itemExpressionTexts,
146
+ itemRecords,
147
+ keyedListSignatureHash: keyedListRecord?.signatureHash,
148
+ claimScope: record.claimScope,
149
+ renderEquivalenceClaim: record.renderEquivalenceClaim,
150
+ runtimeEquivalenceClaim: record.runtimeEquivalenceClaim
151
+ })
152
+ });
153
+ }
154
+
155
+ function collectionItemRecords(collectionKind, itemExpressionTexts) {
156
+ return itemExpressionTexts.map((itemExpressionText, index) => {
157
+ const key = jsxKeyEvidence(itemExpressionText);
158
+ return compactRecord({
159
+ ordinal: index + 1,
160
+ proofStatus: 'static-render-return-collection-item-evidence',
161
+ expressionText: itemExpressionText,
162
+ expressionHash: hashSemanticValue({ kind: 'frontier.lang.projectJsxRenderReturnCollectionItemExpression', text: itemExpressionText }),
163
+ keyPropText: key?.keyPropText,
164
+ keyExpressionText: key?.keyExpressionText,
165
+ keyValue: key?.keyValue,
166
+ keyStatic: key ? key.keyValue !== undefined : undefined,
167
+ signatureHash: hashSemanticValue({
168
+ kind: 'frontier.lang.projectJsxRenderReturnCollectionItem',
169
+ collectionKind,
170
+ ordinal: index + 1,
171
+ expressionText: itemExpressionText,
172
+ keyPropText: key?.keyPropText,
173
+ keyExpressionText: key?.keyExpressionText,
174
+ keyValue: key?.keyValue
175
+ })
176
+ });
177
+ });
178
+ }
179
+
180
+ function keyedListRecordFor(collectionKind, itemRecords) {
181
+ if (!itemRecords.length || itemRecords.some((record) => record.keyStatic === false || (!record.keyPropText && !record.keyExpressionText && record.keyValue === undefined))) return undefined;
182
+ const keyRecords = itemRecords.map((record) => compactRecord({
183
+ ordinal: record.ordinal,
184
+ keyPropText: record.keyPropText,
185
+ keyExpressionText: record.keyExpressionText,
186
+ keyValue: record.keyValue,
187
+ keySourcePropName: record.keySourcePropName,
188
+ keyStatic: record.keyStatic
189
+ }));
190
+ return compactRecord({
191
+ proofStatus: 'static-render-return-keyed-list-evidence',
192
+ reasonCode: 'jsx-render-return-keyed-list-static-evidence',
193
+ claimScope: 'static-list-key-identity-only',
194
+ renderEquivalenceClaim: false,
195
+ runtimeEquivalenceClaim: false,
196
+ keyCount: keyRecords.length,
197
+ keyRecords,
198
+ keyValues: keyRecords.every((record) => record.keyValue !== undefined) ? keyRecords.map((record) => record.keyValue) : undefined,
199
+ signatureHash: hashSemanticValue({
200
+ kind: 'frontier.lang.projectJsxRenderReturnKeyedList',
201
+ collectionKind,
202
+ keyRecords,
203
+ claimScope: 'static-list-key-identity-only',
204
+ renderEquivalenceClaim: false,
205
+ runtimeEquivalenceClaim: false
206
+ })
207
+ });
208
+ }
209
+
210
+ export { jsxRenderReturnCollectionRecord };
@@ -1,12 +1,16 @@
1
1
  import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
- import { jsxRenderReturnCollectionRecord } from './projectSymbolGraphJsxRenderCollections.js';
2
+ import { jsxRenderReturnCollectionRecord } from './projectSymbolGraphJsxRenderReturnCollections.js';
3
3
 
4
4
  function jsxRenderReturnRecords(sourceText) {
5
5
  const statements = returnStatements(sourceText);
6
6
  const implicitArrows = statements.length ? [] : implicitArrowReturnStatements(sourceText);
7
7
  return [...statements, ...implicitArrows]
8
- .filter((statement) => isRenderableReturnExpression(statement.expressionText))
9
- .map((statement, index) => renderReturnRecord(statement, index));
8
+ .map((statement) => ({
9
+ statement,
10
+ collectionRecord: jsxRenderReturnCollectionRecord(statement.expressionText, sourceText)
11
+ }))
12
+ .filter(({ statement, collectionRecord }) => collectionRecord || isRenderableReturnExpression(statement.expressionText))
13
+ .map(({ statement, collectionRecord }, index) => renderReturnRecord(statement, index, collectionRecord));
10
14
  }
11
15
 
12
16
  function jsxRenderReturnRiskEvidence(owner) {
@@ -18,6 +22,8 @@ function jsxRenderReturnRiskEvidence(owner) {
18
22
  const hasLogicalBranchEvidence = records.some((record) => record.logicalBranchRecord);
19
23
  const hasArrayCollectionEvidence = records.some((record) => record.collectionRecord?.collectionKind === 'array-literal');
20
24
  const hasFragmentCollectionEvidence = records.some((record) => String(record.collectionRecord?.collectionKind ?? '').startsWith('fragment-'));
25
+ const hasMapCollectionEvidence = records.some((record) => record.collectionRecord?.collectionKind === 'static-const-array-map');
26
+ const hasKeyedListEvidence = records.some((record) => record.collectionRecord?.keyedListRecord);
21
27
  const renderRiskKinds = ['render-return-boundary', branched ? 'render-return-branch-control-flow' : undefined].filter(Boolean);
22
28
  const hasImplicitArrow = records.some((record) => record.returnKind === 'implicit-arrow-expression');
23
29
  const renderRiskReasonCodes = [
@@ -27,6 +33,8 @@ function jsxRenderReturnRiskEvidence(owner) {
27
33
  hasLogicalBranchEvidence ? 'jsx-render-return-logical-branch-static-evidence' : undefined,
28
34
  hasArrayCollectionEvidence ? 'jsx-render-return-array-static-evidence' : undefined,
29
35
  hasFragmentCollectionEvidence ? 'jsx-render-return-fragment-static-evidence' : undefined,
36
+ hasMapCollectionEvidence ? 'jsx-render-return-static-const-array-map-evidence' : undefined,
37
+ hasKeyedListEvidence ? 'jsx-render-return-keyed-list-static-evidence' : undefined,
30
38
  branched ? 'jsx-render-return-branch-unsupported' : undefined
31
39
  ].filter(Boolean);
32
40
  const record = compactRecord({
@@ -80,12 +88,11 @@ function implicitArrowReturnStatements(sourceText) {
80
88
  return statements;
81
89
  }
82
90
 
83
- function renderReturnRecord(statement, index) {
91
+ function renderReturnRecord(statement, index, collectionRecord) {
84
92
  const expressionText = normalizedText(statement.expressionText);
85
93
  const ifConditionText = normalizedText(statement.ifConditionText);
86
94
  const conditionalBranch = conditionalBranchRecord(expressionText);
87
95
  const logicalBranch = logicalBranchRecord(expressionText);
88
- const collectionRecord = jsxRenderReturnCollectionRecord(expressionText);
89
96
  return compactRecord({
90
97
  ordinal: index + 1,
91
98
  proofStatus: 'static-render-return-evidence',
@@ -0,0 +1,196 @@
1
+ import { isJsxSpreadAttribute } from '../../js-ts-safe-merge-jsx-attribute-parser.js';
2
+ import { parseStaticObjectLiteralExpression } from './projectSymbolGraphJsxStaticLiterals.js';
3
+
4
+ function jsxSpreadPropValueEvidence(tag, attribute, context = {}) {
5
+ const expressionText = jsxSpreadExpressionText(attribute);
6
+ const source = staticObjectSpreadSource(expressionText, attribute, context);
7
+ if (!source?.status) return dynamicSpreadPropValueEvidence(expressionText, source?.reasonCode);
8
+ const precedence = staticSpreadPrecedenceEvidence(tag, attribute, source.entries);
9
+ const propNames = uniqueStrings(source.entries.map((entry) => entry.propName));
10
+ return compactRecord({
11
+ proofStatus: 'static-object-spread-jsx-prop-value-evidence',
12
+ reasonCode: 'jsx-render-prop-spread-static-object-evidence',
13
+ valueKind: 'object-spread',
14
+ valueText: source.valueText,
15
+ expressionText,
16
+ staticSpreadSourceKind: source.sourceKind,
17
+ staticSpreadSourceName: source.sourceName,
18
+ staticSpreadPropEntries: source.entries,
19
+ staticSpreadPropNames: propNames,
20
+ staticSpreadPropCount: propNames.length,
21
+ staticSpreadEffectivePropEntries: precedence.effectiveEntries,
22
+ staticSpreadEffectivePropNames: uniqueStrings(precedence.effectiveEntries.map((entry) => entry.propName)),
23
+ staticSpreadExplicitOverridePropNames: precedence.explicitOverridePropNames,
24
+ staticSpreadOverridesExplicitPropNames: precedence.overridesExplicitPropNames,
25
+ staticSpreadDuplicatePropNames: duplicatePropNames(source.entries),
26
+ staticSpreadPrecedenceStatus: precedence.status,
27
+ claimScope: 'static-object-spread-props-only',
28
+ renderEquivalenceClaim: false
29
+ });
30
+ }
31
+
32
+ function dynamicSpreadPropValueEvidence(expressionText, reasonCode) {
33
+ return {
34
+ proofStatus: 'dynamic-jsx-prop-spread-unsupported',
35
+ reasonCode: 'jsx-render-prop-spread-unsupported',
36
+ valueKind: 'spread-expression',
37
+ expressionText,
38
+ dynamicText: expressionText,
39
+ dynamicBlockerReasonCode: reasonCode ?? jsxPropSpreadDynamicBlockerReasonCode(expressionText),
40
+ claimScope: 'static-object-spread-props-only',
41
+ renderEquivalenceClaim: false
42
+ };
43
+ }
44
+
45
+ function staticObjectSpreadSource(expressionText, attribute, context = {}) {
46
+ const text = normalizedText(expressionText);
47
+ if (!text) return { reasonCode: 'jsx-render-prop-spread-expression-unsupported' };
48
+ if (text.startsWith('{')) {
49
+ const parsed = parseStaticObjectLiteralExpression(text);
50
+ return parsed.value
51
+ ? { status: 'static', sourceKind: 'inline-object-literal', valueText: text, entries: parsed.value.entries }
52
+ : { reasonCode: parsed.reasonCode ?? jsxPropSpreadDynamicBlockerReasonCode(text) };
53
+ }
54
+ if (simpleIdentifierExpression(text)) {
55
+ const resolved = sameFileConstObjectSpreadSource(text, attribute, context);
56
+ return resolved.status ? resolved : { reasonCode: resolved.reasonCode ?? 'jsx-render-prop-spread-reference-unsupported' };
57
+ }
58
+ return { reasonCode: jsxPropSpreadDynamicBlockerReasonCode(text) };
59
+ }
60
+
61
+ function sameFileConstObjectSpreadSource(name, attribute, context = {}) {
62
+ const sourceText = String(context.sourceText ?? '');
63
+ if (!sourceText) return { reasonCode: 'jsx-render-prop-spread-reference-unsupported' };
64
+ const beforeOffset = Number.isFinite(attribute?.start) ? attribute.start : sourceText.length;
65
+ const declarations = constObjectInitializers(sourceText, name).filter((declaration) => declaration.start < beforeOffset);
66
+ if (declarations.length !== 1) return {
67
+ reasonCode: declarations.length > 1 ? 'jsx-render-prop-spread-ambiguous-const-object-unsupported' : 'jsx-render-prop-spread-reference-unsupported'
68
+ };
69
+ const parsed = parseStaticObjectLiteralExpression(declarations[0].initializerText);
70
+ return parsed.value
71
+ ? {
72
+ status: 'static',
73
+ sourceKind: 'same-file-const-object',
74
+ sourceName: name,
75
+ valueText: normalizedText(declarations[0].initializerText),
76
+ entries: parsed.value.entries
77
+ }
78
+ : { reasonCode: parsed.reasonCode ?? 'jsx-render-prop-spread-const-object-dynamic-unsupported' };
79
+ }
80
+
81
+ function constObjectInitializers(sourceText, name) {
82
+ const result = [];
83
+ const pattern = new RegExp(`\\bconst\\s+${escapeRegExp(name)}\\s*=\\s*`, 'g');
84
+ let match;
85
+ while ((match = pattern.exec(sourceText))) {
86
+ const start = match.index;
87
+ const initializerText = readConstInitializerText(sourceText, pattern.lastIndex);
88
+ if (initializerText) result.push({ start, initializerText });
89
+ }
90
+ return result;
91
+ }
92
+
93
+ function readConstInitializerText(sourceText, start) {
94
+ let quote;
95
+ let depth = 0;
96
+ for (let index = start; index < sourceText.length; index += 1) {
97
+ const char = sourceText[index];
98
+ if (quote) {
99
+ if (char === '\\') index += 1;
100
+ else if (char === quote) quote = undefined;
101
+ continue;
102
+ }
103
+ if (char === '"' || char === "'" || char === '`') quote = char;
104
+ else if (char === '(' || char === '[' || char === '{') depth += 1;
105
+ else if (char === ')' || char === ']' || char === '}') depth = Math.max(0, depth - 1);
106
+ else if (depth === 0 && (char === ';' || char === '\n')) return sourceText.slice(start, index).trim();
107
+ }
108
+ return sourceText.slice(start).trim();
109
+ }
110
+
111
+ function staticSpreadPrecedenceEvidence(tag, attribute, entries) {
112
+ const index = attributeIndexInTag(tag, attribute);
113
+ const before = namedAttributesBefore(tag, index);
114
+ const after = namedAttributesAfter(tag, index);
115
+ const lastEntries = lastStaticEntriesByProp(entries);
116
+ const explicitOverridePropNames = uniqueStrings(lastEntries.filter((entry) => after.has(entry.propName)).map((entry) => entry.propName));
117
+ const overridesExplicitPropNames = uniqueStrings(lastEntries.filter((entry) => before.has(entry.propName)).map((entry) => entry.propName));
118
+ const effectiveEntries = lastEntries.filter((entry) => !after.has(entry.propName));
119
+ return {
120
+ effectiveEntries,
121
+ explicitOverridePropNames,
122
+ overridesExplicitPropNames,
123
+ status: staticSpreadPrecedenceStatus(explicitOverridePropNames, overridesExplicitPropNames, duplicatePropNames(entries))
124
+ };
125
+ }
126
+
127
+ function namedAttributesBefore(tag, index) {
128
+ return new Set((tag.attributes ?? []).slice(0, Math.max(0, index)).filter((candidate) => !isJsxSpreadAttribute(candidate)).map((candidate) => candidate.name));
129
+ }
130
+
131
+ function namedAttributesAfter(tag, index) {
132
+ return new Set((tag.attributes ?? []).slice(index + 1).filter((candidate) => !isJsxSpreadAttribute(candidate)).map((candidate) => candidate.name));
133
+ }
134
+
135
+ function staticSpreadPrecedenceStatus(explicitOverridePropNames, overridesExplicitPropNames, duplicateNames) {
136
+ if (explicitOverridePropNames.length && overridesExplicitPropNames.length) return 'static-spread-between-explicit-props';
137
+ if (explicitOverridePropNames.length) return 'static-spread-overridden-by-later-explicit-prop';
138
+ if (overridesExplicitPropNames.length) return 'static-spread-overrides-earlier-explicit-prop';
139
+ if (duplicateNames.length) return 'static-spread-duplicate-prop-last-write-wins';
140
+ return 'static-spread-props-preserved';
141
+ }
142
+
143
+ function attributeIndexInTag(tag, attribute) {
144
+ const attributes = tag.attributes ?? [];
145
+ const direct = attributes.indexOf(attribute);
146
+ if (direct >= 0) return direct;
147
+ return attributes.findIndex((candidate) => candidate.start === attribute?.start && candidate.end === attribute?.end && candidate.name === attribute?.name);
148
+ }
149
+
150
+ function lastStaticEntriesByProp(entries) {
151
+ const byProp = new Map();
152
+ for (const entry of entries) byProp.set(entry.propName, entry);
153
+ return [...byProp.values()].sort((left, right) => left.ordinal - right.ordinal);
154
+ }
155
+
156
+ function duplicatePropNames(entries) {
157
+ const counts = new Map();
158
+ for (const entry of entries) counts.set(entry.propName, (counts.get(entry.propName) ?? 0) + 1);
159
+ return [...counts.entries()].filter(([, count]) => count > 1).map(([name]) => name);
160
+ }
161
+
162
+ function jsxSpreadExpressionText(attribute) {
163
+ const expressionText = normalizedText(attribute?.expressionText);
164
+ if (expressionText) return expressionText;
165
+ const text = String(attribute?.text ?? '').trim();
166
+ const braced = /^\{\s*\.\.\.\s*([\s\S]*?)\s*\}$/.exec(text);
167
+ if (braced) return normalizedText(braced[1]);
168
+ const unbraced = /^\.\.\.\s*([\s\S]*)$/.exec(text);
169
+ return unbraced ? normalizedText(unbraced[1]) : undefined;
170
+ }
171
+
172
+ function jsxPropSpreadDynamicBlockerReasonCode(text) {
173
+ const value = normalizedText(text);
174
+ if (/\[[\s\S]*\]/.test(value)) return 'jsx-render-prop-spread-computed-reference-unsupported';
175
+ if (/^`[\s\S]*\$\{[\s\S]*`$/.test(value)) return 'jsx-render-prop-spread-template-interpolation-unsupported';
176
+ if (/\?\.\s*\(/.test(value) || /\b[A-Za-z_$][\w$]*(?:\s*(?:\.|\?\.)\s*[A-Za-z_$][\w$]*)*\s*\(/.test(value)) return 'jsx-render-prop-spread-call-expression-unsupported';
177
+ if (/^\{/.test(value)) return 'jsx-render-prop-spread-dynamic-object-unsupported';
178
+ if (staticPropReference(value)) return 'jsx-render-prop-spread-reference-unsupported';
179
+ return 'jsx-render-prop-spread-expression-unsupported';
180
+ }
181
+
182
+ function staticPropReference(text) {
183
+ const value = normalizedText(text);
184
+ if (!/^(?:this|[A-Za-z_$][\w$]*)(?:\s*\.\s*[A-Za-z_$][\w$]*)*$/.test(value)) return undefined;
185
+ const normalized = value.replace(/\s+/g, '');
186
+ const path = normalized.split('.');
187
+ return { text: normalized, root: path[0], path };
188
+ }
189
+
190
+ function simpleIdentifierExpression(text) { return /^[A-Za-z_$][\w$]*$/.test(String(text ?? '').trim()); }
191
+ function escapeRegExp(text) { return String(text ?? '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
192
+ function uniqueStrings(values) { return [...new Set(values.filter((value) => typeof value === 'string' && value.length > 0))]; }
193
+ function normalizedText(text) { return String(text ?? '').trim().replace(/\s+/g, ' '); }
194
+ function compactRecord(record) { return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined)); }
195
+
196
+ export { jsxSpreadPropValueEvidence };