@shapeshift-labs/frontier-lang-compiler 0.2.196 → 0.2.198

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/README.md CHANGED
@@ -611,7 +611,8 @@ Recent residual closures represented in the matrix shards:
611
611
  - Module/export/import graph now records no-substitution template dynamic imports and parser-backed CommonJS helper `require` templates as static module edges while keeping expression templates proof-required.
612
612
  - Module/export/import graph now records no-substitution/static template host dependencies for `new URL`, workers, resolver calls, and `importScripts` while leaving expression templates unsupported.
613
613
  - Module/export/import graph now emits dynamic host dependency targets as `<host-dependency>` edges with expression hashes and proof-required unresolved evidence instead of omitting them.
614
- - CSS Modules graph records now infer local class export evidence, source-local composition graph hashes, and export-only ICSS graph hashes from `.module.css` source via `@shapeshift-labs/frontier-lang-css`, while file composition graphs, ICSS imports, generated class-name maps, bundler transform identity, source-map proof, dynamic member access, writes, and helper-call equivalence remain fail-closed without host evidence.
614
+ - CSS Modules graph records now infer local class export evidence, source-local composition graph hashes, export-only ICSS graph hashes, project-source graph hashes for cross-file `composes` / ICSS imports when every referenced `.module.css` source is present and source-inferred, and source-bounded class helper token graph hashes for single top-level `className` helper calls whose CSS Module reads are statically enumerable; package/bundler-only composition, unresolved ICSS imports, generated class-name maps, bundler transform identity, source-map proof, dynamic member access, writes, nested helper calls, and arbitrary helper runtime equivalence remain fail-closed without host evidence.
615
+ - CSS Modules use-site graph evidence now suppresses `css-module-helper-call-unproved` only for source-bounded helper calls with deterministic token enumeration, while preserving transform proof blockers and keeping nested calls/dynamic keys on review routes.
615
616
  - Type/public API graph now includes bounded TypeChecker assignability oracle proof for simple public type aliases while keeping semantic/runtime equivalence claims false.
616
617
  - JSX/TSX graph now records same-file and project-local named/default/barrel-import static component prop passthrough as `jsx-render-component-prop-flow-static-passthrough-evidence` and blocks dynamic callsite values with `jsx-render-component-prop-flow-dynamic-value-unsupported`.
617
618
  - JSX/TSX graph now records static `memo` / `forwardRef` / `observer` / `React.lazy` component wrapper chains as component-wrapper render-risk evidence while keeping render, lazy-load, and runtime equivalence explicitly unproved.
@@ -23,7 +23,9 @@ export interface NativeProjectSymbolGraphCssModuleImportBindingRecord {
23
23
  readonly generatedClassNameMapHash?: string;
24
24
  readonly jsTsUseSiteGraphHash?: string;
25
25
  readonly cssModuleCompositionGraphHash?: string;
26
+ readonly cssModuleCompositionGraphSource?: 'supplied' | 'source-local' | 'project-source' | string;
26
27
  readonly icssGraphHash?: string;
28
+ readonly icssGraphSource?: 'supplied' | 'source-export-only' | 'project-source' | string;
27
29
  readonly bundlerTransformHash?: string;
28
30
  readonly sourceMapProofHash?: string;
29
31
  readonly signatureHash?: string;
@@ -49,6 +51,13 @@ export interface NativeProjectSymbolGraphCssModuleUseSiteRecord {
49
51
  readonly jsxPropRecordId?: string;
50
52
  readonly scopeReferenceRecordId?: string;
51
53
  readonly conditionalRuntimePresence?: boolean;
54
+ readonly helperCallProofLevel?: 'css-module-class-helper-source-bounded-token-graph' | string;
55
+ readonly helperCallGraphHash?: string;
56
+ readonly helperCalleeName?: string;
57
+ readonly helperCalleeRoot?: string;
58
+ readonly helperCalleeSource?: 'known-package-import' | 'local-name-convention' | string;
59
+ readonly helperModuleSpecifier?: string;
60
+ readonly helperImportEdgeId?: string;
52
61
  readonly signatureHash?: string;
53
62
  }
54
63
 
@@ -85,6 +94,13 @@ export interface NativeProjectSymbolGraphCssModuleUseSiteGraphRecord {
85
94
  readonly blockerCount: number;
86
95
  readonly generatedClassNameMapHash?: string;
87
96
  readonly cssModuleExportNamesHash?: string;
97
+ readonly cssModuleCompositionGraphHash?: string;
98
+ readonly cssModuleCompositionGraphSource?: 'supplied' | 'source-local' | 'project-source' | string;
99
+ readonly icssGraphHash?: string;
100
+ readonly icssGraphSource?: 'supplied' | 'source-export-only' | 'project-source' | string;
101
+ readonly cssModuleClassNameHelperGraphHash?: string;
102
+ readonly cssModuleClassNameHelperProofLevels?: readonly string[];
103
+ readonly cssModuleClassNameHelperSources?: readonly string[];
88
104
  readonly bundlerTransformHash?: string;
89
105
  readonly sourceMapProofHash?: string;
90
106
  readonly status: 'ready' | 'blocked' | string;
@@ -0,0 +1,219 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+ import { compactRecord, localKey } from './projectSymbolGraphCssModuleUtils.js';
3
+ import { cssModuleMemberAccess, identifierOccurrences } from './projectSymbolGraphCssModuleMemberAccess.js';
4
+
5
+ const KnownClassHelperModules = new Set(['clsx', 'classnames']);
6
+ const LocalClassHelperNames = new Set(['cx', 'cn', 'clsx', 'classnames', 'classNames']);
7
+
8
+ function cssModuleClassHelperCalleeEvidence(importEdges = []) {
9
+ const result = new Map();
10
+ for (const edge of importEdges ?? []) {
11
+ if (!edge?.sourcePath || !edge.localName || !KnownClassHelperModules.has(edge.moduleSpecifier)) continue;
12
+ if (String(edge.importKind ?? '').startsWith('type')) continue;
13
+ result.set(localKey(edge.sourcePath, edge.localName), compactRecord({
14
+ helperCalleeName: edge.localName,
15
+ helperCalleeRoot: edge.localName,
16
+ helperCalleeSource: 'known-package-import',
17
+ helperModuleSpecifier: edge.moduleSpecifier,
18
+ helperImportKind: edge.importKind,
19
+ helperImportedName: edge.importedName,
20
+ helperImportEdgeId: edge.id
21
+ }));
22
+ }
23
+ return result;
24
+ }
25
+
26
+ function cssModuleClassHelperCallProof(expressionText, binding, prop, helperCalleeEvidence) {
27
+ const call = readTopLevelClassHelperCall(expressionText);
28
+ if (!call) return undefined;
29
+ const calleeEvidence = helperCalleeEvidence.get(localKey(prop.sourcePath, call.calleeRoot))
30
+ ?? localClassHelperCalleeEvidence(call);
31
+ if (!calleeEvidence) return undefined;
32
+ if (classHelperCallHasUnsafeSyntax(expressionText, call.open)) return undefined;
33
+ const accessRecords = cssModuleHelperAccessRecords(expressionText, binding);
34
+ if (!accessRecords.length) return undefined;
35
+ const helperCallGraphHash = hashSemanticValue({
36
+ kind: 'frontier.lang.cssModuleClassNameHelperCallGraph.v1',
37
+ proofLevel: 'css-module-class-helper-source-bounded-token-graph',
38
+ sourcePath: prop.sourcePath,
39
+ sourceHash: prop.sourceHash,
40
+ cssModuleImportBindingId: binding.id,
41
+ cssModuleSourcePath: binding.cssModuleSourcePath,
42
+ cssModuleHash: binding.cssModuleHash,
43
+ jsxPropRecordId: prop.id,
44
+ helperCalleeName: call.calleeText,
45
+ helperCalleeRoot: call.calleeRoot,
46
+ helperCalleeSource: calleeEvidence.helperCalleeSource,
47
+ helperModuleSpecifier: calleeEvidence.helperModuleSpecifier,
48
+ expressionText,
49
+ accessRecords
50
+ });
51
+ return compactRecord({
52
+ proofLevel: 'css-module-class-helper-source-bounded-token-graph',
53
+ helperCallGraphHash,
54
+ helperCalleeName: call.calleeText,
55
+ helperCalleeRoot: call.calleeRoot,
56
+ helperCalleeSource: calleeEvidence.helperCalleeSource,
57
+ helperModuleSpecifier: calleeEvidence.helperModuleSpecifier,
58
+ helperImportEdgeId: calleeEvidence.helperImportEdgeId
59
+ });
60
+ }
61
+
62
+ function cssModuleHelperArgumentIsConditional(expressionText, occurrenceStart) {
63
+ const prefix = expressionText.slice(0, occurrenceStart);
64
+ const suffix = expressionText.slice(occurrenceStart);
65
+ const lastComma = prefix.lastIndexOf(',');
66
+ const argumentPrefix = prefix.slice(lastComma + 1);
67
+ const nextComma = suffix.indexOf(',');
68
+ const argumentText = `${argumentPrefix}${nextComma === -1 ? suffix : suffix.slice(0, nextComma)}`;
69
+ return /(?:&&|\|\||\?)/.test(argumentText);
70
+ }
71
+
72
+ function cssModuleMemberWriteOperation(sourceText, start, end) {
73
+ if (hasPrefixUpdateOperator(sourceText, start)) return 'update';
74
+ let index = end;
75
+ while (index < sourceText.length && /\s/.test(sourceText[index])) index += 1;
76
+ if (sourceText.slice(index, index + 2) === '++' || sourceText.slice(index, index + 2) === '--') return 'update';
77
+ return assignmentOperatorAt(sourceText, index) ? 'assignment' : undefined;
78
+ }
79
+
80
+ function localClassHelperCalleeEvidence(call) {
81
+ return LocalClassHelperNames.has(call.calleeRoot)
82
+ ? { helperCalleeName: call.calleeText, helperCalleeRoot: call.calleeRoot, helperCalleeSource: 'local-name-convention' }
83
+ : undefined;
84
+ }
85
+
86
+ function cssModuleHelperAccessRecords(expressionText, binding) {
87
+ const records = [];
88
+ for (const occurrence of identifierOccurrences(expressionText, binding.localName)) {
89
+ const access = cssModuleMemberAccess(expressionText, occurrence.end);
90
+ if (!access || access.status === 'blocked') return [];
91
+ if (cssModuleMemberWriteOperation(expressionText, occurrence.start, access.end)) return [];
92
+ records.push({
93
+ receiverLocalName: binding.localName,
94
+ exportName: access.memberName,
95
+ accessKind: access.accessKind,
96
+ expressionText: expressionText.slice(occurrence.start, access.end),
97
+ start: occurrence.start,
98
+ end: access.end,
99
+ conditionalRuntimePresence: cssModuleHelperArgumentIsConditional(expressionText, occurrence.start) || undefined
100
+ });
101
+ }
102
+ return records;
103
+ }
104
+
105
+ function readTopLevelClassHelperCall(expressionText) {
106
+ const value = String(expressionText ?? '').trim();
107
+ const callee = /^(?:[A-Za-z_$][\w$]*|this)(?:\s*(?:\.|\?\.)\s*[A-Za-z_$][\w$]*)*/.exec(value)?.[0];
108
+ if (!callee) return undefined;
109
+ let index = callee.length;
110
+ while (index < value.length && /\s/.test(value[index])) index += 1;
111
+ if (value[index] !== '(') return undefined;
112
+ const close = findMatchingDelimiter(value, index, '(', ')');
113
+ if (close === -1) return undefined;
114
+ let tail = close + 1;
115
+ while (tail < value.length && /\s/.test(value[tail])) tail += 1;
116
+ if (tail !== value.length) return undefined;
117
+ const calleeText = callee.replace(/\s+/g, '');
118
+ return { calleeText, calleeRoot: calleeText.split(/[?.]+/)[0], open: index };
119
+ }
120
+
121
+ function classHelperCallHasUnsafeSyntax(expressionText, topLevelOpen) {
122
+ const value = String(expressionText ?? '');
123
+ if (value.includes('...') || value.includes('=>') || /\b(?:function|class|new|await|yield)\b/.test(value)) return true;
124
+ if (/`[\s\S]*\$\{[\s\S]*`/.test(value)) return true;
125
+ if (/(?:\+\+|--|\*\*=|>>>=|<<=|>>=|&&=|\|\|=|\?\?=|\+=|-=|\*=|\/=|%=|&=|\|=|\^=)/.test(value)) return true;
126
+ if (/(^|[^=!<>])=(?!=|>)/.test(value)) return true;
127
+ for (const callOpen of callOpenIndexes(value)) if (callOpen !== topLevelOpen) return true;
128
+ return !delimitersAreBalanced(value);
129
+ }
130
+
131
+ function callOpenIndexes(sourceText) {
132
+ const indexes = [];
133
+ for (let index = 0; index < sourceText.length; index += 1) {
134
+ const char = sourceText[index];
135
+ if (char === '\'' || char === '"' || char === '`') {
136
+ const end = readQuotedOrStaticTemplateEnd(sourceText, index, char);
137
+ if (end === -1) return [...indexes, -1];
138
+ index = end;
139
+ continue;
140
+ }
141
+ if (char !== '(') continue;
142
+ let before = index - 1;
143
+ while (before >= 0 && /\s/.test(sourceText[before])) before -= 1;
144
+ if (before >= 0 && /[A-Za-z0-9_$\].]/.test(sourceText[before])) indexes.push(index);
145
+ }
146
+ return indexes;
147
+ }
148
+
149
+ function delimitersAreBalanced(sourceText) {
150
+ const stack = [];
151
+ const pairs = { '(': ')', '[': ']', '{': '}' };
152
+ for (let index = 0; index < sourceText.length; index += 1) {
153
+ const char = sourceText[index];
154
+ if (char === '\'' || char === '"' || char === '`') {
155
+ const end = readQuotedOrStaticTemplateEnd(sourceText, index, char);
156
+ if (end === -1) return false;
157
+ index = end;
158
+ continue;
159
+ }
160
+ if (pairs[char]) stack.push(pairs[char]);
161
+ else if ((char === ')' || char === ']' || char === '}') && stack.pop() !== char) return false;
162
+ }
163
+ return stack.length === 0;
164
+ }
165
+
166
+ function findMatchingDelimiter(sourceText, open, openChar, closeChar) {
167
+ let depth = 0;
168
+ for (let index = open; index < sourceText.length; index += 1) {
169
+ const char = sourceText[index];
170
+ if (char === '\'' || char === '"' || char === '`') {
171
+ const end = readQuotedOrStaticTemplateEnd(sourceText, index, char);
172
+ if (end === -1) return -1;
173
+ index = end;
174
+ continue;
175
+ }
176
+ if (char === openChar) depth += 1;
177
+ else if (char === closeChar && --depth === 0) return index;
178
+ }
179
+ return -1;
180
+ }
181
+
182
+ function readQuotedOrStaticTemplateEnd(sourceText, start, quote) {
183
+ for (let index = start + 1; index < sourceText.length; index += 1) {
184
+ const char = sourceText[index];
185
+ if (char === '\\') {
186
+ index += 1;
187
+ continue;
188
+ }
189
+ if (quote === '`' && char === '$' && sourceText[index + 1] === '{') return -1;
190
+ if (char === quote) return index;
191
+ }
192
+ return -1;
193
+ }
194
+
195
+ function assignmentOperatorAt(code, index) {
196
+ for (const operator of ['>>>=', '**=', '<<=', '>>=', '&&=', '||=', '??=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '=']) {
197
+ if (!code.startsWith(operator, index)) continue;
198
+ if (operator === '=' && (code[index + 1] === '=' || code[index + 1] === '>')) return undefined;
199
+ return operator;
200
+ }
201
+ return undefined;
202
+ }
203
+
204
+ function hasPrefixUpdateOperator(code, tokenStart) {
205
+ let index = tokenStart - 1;
206
+ while (index >= 0 && /\s/.test(code[index])) index -= 1;
207
+ const operator = code.slice(index - 1, index + 1);
208
+ if (operator !== '++' && operator !== '--') return false;
209
+ let before = index - 2;
210
+ while (before >= 0 && /\s/.test(code[before])) before -= 1;
211
+ return before < 0 || !/[A-Za-z0-9_$)\]]/.test(code[before]);
212
+ }
213
+
214
+ export {
215
+ cssModuleClassHelperCalleeEvidence,
216
+ cssModuleClassHelperCallProof,
217
+ cssModuleHelperArgumentIsConditional,
218
+ cssModuleMemberWriteOperation
219
+ };
@@ -0,0 +1,255 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+
3
+ function withProjectCssModuleDependencyGraphs(cssSourcesByPath) {
4
+ const enriched = new Map(cssSourcesByPath);
5
+ for (const [sourcePath, cssSource] of cssSourcesByPath.entries()) {
6
+ const cssModuleEvidence = cssSource?.cssModuleEvidence;
7
+ if (!cssModuleEvidence || cssSource.cssModuleEvidenceSource !== 'inferred-source') continue;
8
+ const computedCompositionGraphHash = cssModuleEvidence.cssModuleCompositionGraphHash
9
+ ? undefined
10
+ : projectCssModuleCompositionGraphHash(sourcePath, enriched);
11
+ const computedIcssGraphHash = cssModuleEvidence.icssGraphHash
12
+ ? undefined
13
+ : projectCssModuleIcssGraphHash(sourcePath, enriched);
14
+ if (!computedCompositionGraphHash && !computedIcssGraphHash) continue;
15
+ enriched.set(sourcePath, {
16
+ ...cssSource,
17
+ cssModuleEvidence: {
18
+ ...cssModuleEvidence,
19
+ cssModuleCompositionGraphHash: computedCompositionGraphHash ?? cssModuleEvidence.cssModuleCompositionGraphHash,
20
+ cssModuleCompositionGraphSource: computedCompositionGraphHash ? 'project-source' : cssModuleEvidence.cssModuleCompositionGraphSource,
21
+ icssGraphHash: computedIcssGraphHash ?? cssModuleEvidence.icssGraphHash,
22
+ icssGraphSource: computedIcssGraphHash ? 'project-source' : cssModuleEvidence.icssGraphSource,
23
+ proofGaps: cssModuleProofGapsWithout(cssModuleEvidence.proofGaps, [
24
+ ...(computedCompositionGraphHash ? ['css-module-composition-resolution-unproved'] : []),
25
+ ...(computedIcssGraphHash ? ['css-module-icss-graph-unproved'] : [])
26
+ ])
27
+ }
28
+ });
29
+ }
30
+ return enriched;
31
+ }
32
+
33
+ function projectCssModuleCompositionGraphHash(sourcePath, cssSourcesByPath) {
34
+ const source = projectCssModuleSource(sourcePath, cssSourcesByPath);
35
+ const compositions = cssModuleCompositions(source?.cssModuleEvidence);
36
+ if (!compositions.length) return undefined;
37
+ const graph = {
38
+ sources: new Map(),
39
+ edges: []
40
+ };
41
+ if (!visitCssModuleCompositionSource(sourcePath, cssSourcesByPath, graph, [])) return undefined;
42
+ return hashSemanticValue({
43
+ kind: 'frontier.lang.css.modules.projectCompositionGraph.v1',
44
+ rootSourcePath: sourcePath,
45
+ sources: sortedValues(graph.sources),
46
+ edges: graph.edges.sort(stableRecordCompare)
47
+ });
48
+ }
49
+
50
+ function visitCssModuleCompositionSource(sourcePath, cssSourcesByPath, graph, stack) {
51
+ if (graph.sources.has(sourcePath)) return true;
52
+ if (stack.includes(sourcePath)) return false;
53
+ const cssSource = projectCssModuleSource(sourcePath, cssSourcesByPath);
54
+ const evidence = cssSource?.cssModuleEvidence;
55
+ if (!evidence) return false;
56
+ const exportNames = cssModuleExportNames(evidence);
57
+ graph.sources.set(sourcePath, {
58
+ sourcePath,
59
+ sourceHash: evidence.sourceHash ?? cssSource.sourceHash,
60
+ moduleHash: evidence.moduleHash,
61
+ exportNames
62
+ });
63
+ for (const composition of cssModuleCompositions(evidence)) {
64
+ const sourceKind = composition.sourceKind ?? (composition.source ? 'file' : 'local');
65
+ if (sourceKind === 'file') {
66
+ const targetSourcePath = resolveRelativeCssSourcePath(sourcePath, composition.source);
67
+ const target = projectCssModuleSource(targetSourcePath, cssSourcesByPath);
68
+ if (!target) return false;
69
+ const targetExportNames = cssModuleExportNames(target.cssModuleEvidence);
70
+ if (composition.names?.some((name) => !targetExportNames.includes(name))) return false;
71
+ graph.edges.push(compositionGraphEdge(composition, {
72
+ sourcePath,
73
+ sourceKind,
74
+ targetSourcePath,
75
+ targetSourceHash: target.cssModuleEvidence?.sourceHash ?? target.sourceHash,
76
+ targetModuleHash: target.cssModuleEvidence?.moduleHash
77
+ }));
78
+ if (!visitCssModuleCompositionSource(targetSourcePath, cssSourcesByPath, graph, [...stack, sourcePath])) return false;
79
+ continue;
80
+ }
81
+ if (sourceKind !== 'local' && sourceKind !== 'global') return false;
82
+ graph.edges.push(compositionGraphEdge(composition, { sourcePath, sourceKind }));
83
+ }
84
+ return true;
85
+ }
86
+
87
+ function projectCssModuleIcssGraphHash(sourcePath, cssSourcesByPath) {
88
+ const source = projectCssModuleSource(sourcePath, cssSourcesByPath);
89
+ const evidence = source?.cssModuleEvidence;
90
+ if (!cssModuleIcssImports(evidence).length && !cssModuleIcssExports(evidence).length) return undefined;
91
+ const graph = {
92
+ sources: new Map(),
93
+ imports: [],
94
+ exports: []
95
+ };
96
+ if (!visitCssModuleIcssSource(sourcePath, cssSourcesByPath, graph, [])) return undefined;
97
+ return hashSemanticValue({
98
+ kind: 'frontier.lang.css.modules.projectIcssGraph.v1',
99
+ rootSourcePath: sourcePath,
100
+ sources: sortedValues(graph.sources),
101
+ imports: graph.imports.sort(stableRecordCompare),
102
+ exports: graph.exports.sort(stableRecordCompare)
103
+ });
104
+ }
105
+
106
+ function visitCssModuleIcssSource(sourcePath, cssSourcesByPath, graph, stack) {
107
+ if (graph.sources.has(sourcePath)) return true;
108
+ if (stack.includes(sourcePath)) return false;
109
+ const cssSource = projectCssModuleSource(sourcePath, cssSourcesByPath);
110
+ const evidence = cssSource?.cssModuleEvidence;
111
+ if (!evidence) return false;
112
+ graph.sources.set(sourcePath, {
113
+ sourcePath,
114
+ sourceHash: evidence.sourceHash ?? cssSource.sourceHash,
115
+ moduleHash: evidence.moduleHash,
116
+ icssExportNames: cssModuleIcssExportNames(evidence)
117
+ });
118
+ for (const exported of cssModuleIcssExports(evidence)) {
119
+ graph.exports.push({
120
+ sourcePath,
121
+ name: exported.name,
122
+ value: exported.value,
123
+ exportHash: exported.exportHash
124
+ });
125
+ }
126
+ for (const imported of cssModuleIcssImports(evidence)) {
127
+ const targetSourcePath = resolveRelativeCssSourcePath(sourcePath, imported.source);
128
+ const target = projectCssModuleSource(targetSourcePath, cssSourcesByPath);
129
+ if (!target) return false;
130
+ const targetExportNames = cssModuleIcssExportNames(target.cssModuleEvidence);
131
+ if (!targetExportNames.includes(imported.importedName)) return false;
132
+ graph.imports.push({
133
+ sourcePath,
134
+ targetSourcePath,
135
+ targetSourceHash: target.cssModuleEvidence?.sourceHash ?? target.sourceHash,
136
+ targetModuleHash: target.cssModuleEvidence?.moduleHash,
137
+ importedName: imported.importedName,
138
+ localName: imported.localName,
139
+ importHash: imported.importHash
140
+ });
141
+ if (!visitCssModuleIcssSource(targetSourcePath, cssSourcesByPath, graph, [...stack, sourcePath])) return false;
142
+ }
143
+ return true;
144
+ }
145
+
146
+ function projectCssModuleSource(sourcePath, cssSourcesByPath) {
147
+ if (!sourcePath) return undefined;
148
+ const cssSource = cssSourcesByPath.get(sourcePath);
149
+ if (!cssSource?.cssModuleEvidence || cssSource.cssModuleEvidenceSource !== 'inferred-source') return undefined;
150
+ return cssSource;
151
+ }
152
+
153
+ function compositionGraphEdge(composition, extra) {
154
+ return {
155
+ sourcePath: extra.sourcePath,
156
+ localName: composition.localName,
157
+ names: [...(composition.names ?? [])],
158
+ source: composition.source,
159
+ sourceKind: extra.sourceKind,
160
+ targetSourcePath: extra.targetSourcePath,
161
+ targetSourceHash: extra.targetSourceHash,
162
+ targetModuleHash: extra.targetModuleHash,
163
+ compositionHash: composition.compositionHash
164
+ };
165
+ }
166
+
167
+ function resolveRelativeCssSourcePath(sourcePath, specifier) {
168
+ if (!sourcePath || !specifier || !String(specifier).startsWith('.')) return undefined;
169
+ const cleanSpecifier = String(specifier).split(/[?#]/)[0];
170
+ const parts = String(sourcePath).split('/');
171
+ parts.pop();
172
+ for (const part of cleanSpecifier.split('/')) {
173
+ if (!part || part === '.') continue;
174
+ if (part === '..') {
175
+ if (!parts.length) return undefined;
176
+ parts.pop();
177
+ continue;
178
+ }
179
+ parts.push(part);
180
+ }
181
+ return parts.join('/');
182
+ }
183
+
184
+ function cssModuleCompositions(evidence) {
185
+ return arrayValue(evidence?.compositions);
186
+ }
187
+
188
+ function cssModuleIcssImports(evidence) {
189
+ return [
190
+ ...arrayValue(evidence?.icssImports),
191
+ ...arrayValue(evidence?.icss?.imports)
192
+ ];
193
+ }
194
+
195
+ function cssModuleIcssExports(evidence) {
196
+ return [
197
+ ...arrayValue(evidence?.icssExports),
198
+ ...arrayValue(evidence?.icss?.exports)
199
+ ];
200
+ }
201
+
202
+ function cssModuleExportNames(evidence) {
203
+ return uniqueSortedStrings([
204
+ ...arrayValue(evidence?.exportNames),
205
+ ...arrayValue(evidence?.localClassNames),
206
+ ...arrayValue(evidence?.classNames),
207
+ ...cssModuleExportRecordNames(evidence?.exports),
208
+ ...objectKeys(evidence?.exports),
209
+ ...objectKeys(evidence?.generatedClassNameMap),
210
+ ...objectKeys(evidence?.classMap)
211
+ ]);
212
+ }
213
+
214
+ function cssModuleIcssExportNames(evidence) {
215
+ return uniqueSortedStrings([
216
+ ...arrayValue(evidence?.icssExportNames),
217
+ ...cssModuleExportRecordNames(evidence?.icssExports),
218
+ ...objectKeys(evidence?.icssExports),
219
+ ...cssModuleExportRecordNames(evidence?.icss?.exports),
220
+ ...objectKeys(evidence?.icss?.exports)
221
+ ]);
222
+ }
223
+
224
+ function cssModuleExportRecordNames(value) {
225
+ return Array.isArray(value)
226
+ ? value.map((entry) => entry?.name ?? entry?.localName ?? entry?.exportedName).filter(Boolean)
227
+ : [];
228
+ }
229
+
230
+ function cssModuleProofGapsWithout(proofGaps, codes) {
231
+ const remove = new Set(codes);
232
+ return arrayValue(proofGaps).filter((gap) => !remove.has(gap?.code));
233
+ }
234
+
235
+ function sortedValues(map) {
236
+ return [...map.values()].sort(stableRecordCompare);
237
+ }
238
+
239
+ function stableRecordCompare(left, right) {
240
+ return JSON.stringify(left).localeCompare(JSON.stringify(right));
241
+ }
242
+
243
+ function arrayValue(value) {
244
+ return Array.isArray(value) ? value : [];
245
+ }
246
+
247
+ function objectKeys(value) {
248
+ return value && typeof value === 'object' && !Array.isArray(value) ? Object.keys(value) : [];
249
+ }
250
+
251
+ function uniqueSortedStrings(values) {
252
+ return [...new Set(values.filter((value) => typeof value === 'string' && value.length > 0))].sort();
253
+ }
254
+
255
+ export { withProjectCssModuleDependencyGraphs };
@@ -0,0 +1,89 @@
1
+ import { hashSemanticValue } from '@shapeshift-labs/frontier-lang-kernel';
2
+ import { semanticSpanForHash } from './projectSymbolGraphCssModuleUtils.js';
3
+
4
+ function cssModuleUseSiteGraphHashes(cssModuleSourcePath, bindings, graphUseSites, graphBlockers) {
5
+ const helperGraph = cssModuleClassNameHelperGraphSummary(graphUseSites);
6
+ return {
7
+ jsTsUseSiteGraphHash: hashSemanticValue({
8
+ kind: 'frontier.lang.cssModuleJsTsUseSiteGraph.v1',
9
+ cssModuleSourcePath,
10
+ bindings: bindings.map(cssModuleUseSiteBindingSignature).sort(stableStringCompare),
11
+ useSites: graphUseSites.map(cssModuleUseSiteSignature).sort(stableStringCompare),
12
+ blockers: graphBlockers.map(cssModuleUseSiteBlockerSignature).sort(stableStringCompare)
13
+ }),
14
+ graphHash: hashSemanticValue({
15
+ kind: 'frontier.lang.cssModuleUseSiteGraph.v1',
16
+ cssModuleSourcePath,
17
+ bindings: bindings.map((binding) => binding.signatureHash).sort(),
18
+ useSites: graphUseSites.map((site) => site.signatureHash).sort(),
19
+ blockers: graphBlockers.map((blocker) => blocker.signatureHash).sort()
20
+ }),
21
+ ...helperGraph
22
+ };
23
+ }
24
+
25
+ function cssModuleUseSiteBindingSignature(binding) {
26
+ return JSON.stringify({
27
+ sourcePath: binding.sourcePath,
28
+ sourceHash: binding.sourceHash,
29
+ moduleSpecifier: binding.moduleSpecifier,
30
+ resolvedModulePath: binding.resolvedModulePath,
31
+ importKind: binding.importKind,
32
+ importedName: binding.importedName,
33
+ localName: binding.localName
34
+ });
35
+ }
36
+
37
+ function cssModuleUseSiteSignature(site) {
38
+ return JSON.stringify({
39
+ jsSourcePath: site.jsSourcePath,
40
+ jsSourceHash: site.jsSourceHash,
41
+ exportName: site.exportName,
42
+ useSiteKind: site.useSiteKind,
43
+ accessKind: site.accessKind,
44
+ receiverLocalName: site.receiverLocalName,
45
+ localReferenceName: site.localReferenceName,
46
+ expressionText: site.expressionText,
47
+ sourceSpan: semanticSpanForHash(site.sourceSpan),
48
+ conditionalRuntimePresence: site.conditionalRuntimePresence,
49
+ helperCallProofLevel: site.helperCallProofLevel,
50
+ helperCallGraphHash: site.helperCallGraphHash,
51
+ helperCalleeName: site.helperCalleeName,
52
+ helperCalleeSource: site.helperCalleeSource,
53
+ helperModuleSpecifier: site.helperModuleSpecifier
54
+ });
55
+ }
56
+
57
+ function cssModuleUseSiteBlockerSignature(blocker) {
58
+ return JSON.stringify({
59
+ sourcePath: blocker.sourcePath,
60
+ sourceHash: blocker.sourceHash,
61
+ moduleSpecifier: blocker.moduleSpecifier,
62
+ localName: blocker.localName,
63
+ expressionText: blocker.expressionText,
64
+ reasonCode: blocker.reasonCode,
65
+ writeOperation: blocker.writeOperation,
66
+ sourceSpan: semanticSpanForHash(blocker.sourceSpan)
67
+ });
68
+ }
69
+
70
+ function cssModuleClassNameHelperGraphSummary(useSites) {
71
+ const helperHashes = uniqueSortedStrings(useSites.map((site) => site.helperCallGraphHash));
72
+ if (!helperHashes.length) return {};
73
+ return {
74
+ cssModuleClassNameHelperGraphHash: hashSemanticValue({
75
+ kind: 'frontier.lang.cssModuleClassNameHelperGraph.v1',
76
+ helperCallGraphHashes: helperHashes
77
+ }),
78
+ cssModuleClassNameHelperProofLevels: uniqueSortedStrings(useSites.map((site) => site.helperCallProofLevel)),
79
+ cssModuleClassNameHelperSources: uniqueSortedStrings(useSites.map((site) => site.helperCalleeSource))
80
+ };
81
+ }
82
+
83
+ function stableStringCompare(left, right) { return left.localeCompare(right); }
84
+
85
+ function uniqueSortedStrings(values) {
86
+ return [...new Set(values.filter((value) => typeof value === 'string' && value.length > 0))].sort();
87
+ }
88
+
89
+ export { cssModuleUseSiteGraphHashes };
@@ -7,6 +7,7 @@ import {
7
7
  semanticSpanForHash,
8
8
  sourceSpanForRange
9
9
  } from './projectSymbolGraphCssModuleUtils.js';
10
+ import { cssModuleUseSiteGraphHashes } from './projectSymbolGraphCssModuleGraphHashes.js';
10
11
 
11
12
  function cssModuleImportBindingRecord(edge, index, documentsById, cssSourcesByPath) {
12
13
  const cssSourcePath = edge.resolvedModulePath ?? cssModuleSpecifierPath(edge.moduleSpecifier);
@@ -54,7 +55,9 @@ function cssModuleImportBindingRecord(edge, index, documentsById, cssSourcesByPa
54
55
  generatedClassNameMapHash: cssModuleEvidence?.generatedClassNameMapHash ?? cssModuleGeneratedClassNameMapHash(cssModuleEvidence),
55
56
  jsTsUseSiteGraphHash: cssModuleEvidence?.jsTsUseSiteGraphHash,
56
57
  cssModuleCompositionGraphHash: cssModuleEvidence?.cssModuleCompositionGraphHash,
58
+ cssModuleCompositionGraphSource: cssModuleEvidence?.cssModuleCompositionGraphSource,
57
59
  icssGraphHash: cssModuleEvidence?.icssGraphHash,
60
+ icssGraphSource: cssModuleEvidence?.icssGraphSource,
58
61
  bundlerTransformHash: cssSource?.bundlerTransformHash,
59
62
  sourceMapProofHash: cssSource?.sourceMapProofHash,
60
63
  signatureHash
@@ -69,7 +72,12 @@ function cssModuleUseSiteRecord(binding, input) {
69
72
  accessKind: input.accessKind,
70
73
  exportName: input.exportName,
71
74
  expressionText: input.expressionText,
72
- sourceSpan: semanticSpanForHash(input.sourceSpan)
75
+ sourceSpan: semanticSpanForHash(input.sourceSpan),
76
+ helperCallProofLevel: input.helperCallProofLevel,
77
+ helperCallGraphHash: input.helperCallGraphHash,
78
+ helperCalleeName: input.helperCalleeName,
79
+ helperCalleeSource: input.helperCalleeSource,
80
+ helperModuleSpecifier: input.helperModuleSpecifier
73
81
  });
74
82
  return compactRecord({
75
83
  id: `css_module_use_${idFragment(binding.id)}_${idFragment(signatureHash)}`,
@@ -91,6 +99,13 @@ function cssModuleUseSiteRecord(binding, input) {
91
99
  jsxPropRecordId: input.jsxPropRecordId,
92
100
  scopeReferenceRecordId: input.scopeReferenceRecordId,
93
101
  conditionalRuntimePresence: input.conditionalRuntimePresence,
102
+ helperCallProofLevel: input.helperCallProofLevel,
103
+ helperCallGraphHash: input.helperCallGraphHash,
104
+ helperCalleeName: input.helperCalleeName,
105
+ helperCalleeRoot: input.helperCalleeRoot,
106
+ helperCalleeSource: input.helperCalleeSource,
107
+ helperModuleSpecifier: input.helperModuleSpecifier,
108
+ helperImportEdgeId: input.helperImportEdgeId,
94
109
  signatureHash
95
110
  });
96
111
  }
@@ -185,20 +200,7 @@ function cssModuleUseSiteGraphRecords(importBindings, useSites, blockers) {
185
200
  }
186
201
 
187
202
  function cssModuleUseSiteGraphRecord(cssModuleSourcePath, bindings, graphUseSites, graphBlockers) {
188
- const jsTsUseSiteGraphHash = hashSemanticValue({
189
- kind: 'frontier.lang.cssModuleJsTsUseSiteGraph.v1',
190
- cssModuleSourcePath,
191
- bindings: bindings.map(cssModuleUseSiteBindingSignature).sort(stableStringCompare),
192
- useSites: graphUseSites.map(cssModuleUseSiteSignature).sort(stableStringCompare),
193
- blockers: graphBlockers.map(cssModuleUseSiteBlockerSignature).sort(stableStringCompare)
194
- });
195
- const graphHash = hashSemanticValue({
196
- kind: 'frontier.lang.cssModuleUseSiteGraph.v1',
197
- cssModuleSourcePath,
198
- bindings: bindings.map((binding) => binding.signatureHash).sort(),
199
- useSites: graphUseSites.map((site) => site.signatureHash).sort(),
200
- blockers: graphBlockers.map((blocker) => blocker.signatureHash).sort()
201
- });
203
+ const graphHashes = cssModuleUseSiteGraphHashes(cssModuleSourcePath, bindings, graphUseSites, graphBlockers);
202
204
  return compactRecord({
203
205
  kind: 'frontier.lang.cssModuleUseSiteGraph',
204
206
  version: 1,
@@ -213,58 +215,23 @@ function cssModuleUseSiteGraphRecord(cssModuleSourcePath, bindings, graphUseSite
213
215
  blockerCount: graphBlockers.length,
214
216
  generatedClassNameMapHash: bindings.find((binding) => binding.generatedClassNameMapHash)?.generatedClassNameMapHash,
215
217
  cssModuleExportNamesHash: bindings.find((binding) => binding.cssModuleExportNamesHash)?.cssModuleExportNamesHash,
218
+ cssModuleCompositionGraphHash: bindings.find((binding) => binding.cssModuleCompositionGraphHash)?.cssModuleCompositionGraphHash,
219
+ cssModuleCompositionGraphSource: bindings.find((binding) => binding.cssModuleCompositionGraphSource)?.cssModuleCompositionGraphSource,
220
+ icssGraphHash: bindings.find((binding) => binding.icssGraphHash)?.icssGraphHash,
221
+ icssGraphSource: bindings.find((binding) => binding.icssGraphSource)?.icssGraphSource,
222
+ cssModuleClassNameHelperGraphHash: graphHashes.cssModuleClassNameHelperGraphHash,
223
+ cssModuleClassNameHelperProofLevels: graphHashes.cssModuleClassNameHelperProofLevels,
224
+ cssModuleClassNameHelperSources: graphHashes.cssModuleClassNameHelperSources,
216
225
  bundlerTransformHash: bindings.find((binding) => binding.bundlerTransformHash)?.bundlerTransformHash,
217
226
  sourceMapProofHash: bindings.find((binding) => binding.sourceMapProofHash)?.sourceMapProofHash,
218
- jsTsUseSiteGraphHash,
227
+ jsTsUseSiteGraphHash: graphHashes.jsTsUseSiteGraphHash,
219
228
  status: graphBlockers.length ? 'blocked' : 'ready',
220
- graphHash,
229
+ graphHash: graphHashes.graphHash,
221
230
  autoMergeClaim: false,
222
231
  semanticEquivalenceClaim: false
223
232
  });
224
233
  }
225
234
 
226
- function cssModuleUseSiteBindingSignature(binding) {
227
- return JSON.stringify({
228
- sourcePath: binding.sourcePath,
229
- sourceHash: binding.sourceHash,
230
- moduleSpecifier: binding.moduleSpecifier,
231
- resolvedModulePath: binding.resolvedModulePath,
232
- importKind: binding.importKind,
233
- importedName: binding.importedName,
234
- localName: binding.localName
235
- });
236
- }
237
-
238
- function cssModuleUseSiteSignature(site) {
239
- return JSON.stringify({
240
- jsSourcePath: site.jsSourcePath,
241
- jsSourceHash: site.jsSourceHash,
242
- exportName: site.exportName,
243
- useSiteKind: site.useSiteKind,
244
- accessKind: site.accessKind,
245
- receiverLocalName: site.receiverLocalName,
246
- localReferenceName: site.localReferenceName,
247
- expressionText: site.expressionText,
248
- sourceSpan: semanticSpanForHash(site.sourceSpan),
249
- conditionalRuntimePresence: site.conditionalRuntimePresence
250
- });
251
- }
252
-
253
- function cssModuleUseSiteBlockerSignature(blocker) {
254
- return JSON.stringify({
255
- sourcePath: blocker.sourcePath,
256
- sourceHash: blocker.sourceHash,
257
- moduleSpecifier: blocker.moduleSpecifier,
258
- localName: blocker.localName,
259
- expressionText: blocker.expressionText,
260
- reasonCode: blocker.reasonCode,
261
- writeOperation: blocker.writeOperation,
262
- sourceSpan: semanticSpanForHash(blocker.sourceSpan)
263
- });
264
- }
265
-
266
- function stableStringCompare(left, right) { return left.localeCompare(right); }
267
-
268
235
  function cssModuleExportHash(binding, exportName) {
269
236
  if (!exportName || !Array.isArray(binding.cssModuleExportNames) || !binding.cssModuleExportNames.includes(exportName)) return undefined;
270
237
  return hashSemanticValue({ kind: 'frontier.lang.cssModuleExport.v1', cssModuleHash: binding.cssModuleHash, exportName });
@@ -16,6 +16,12 @@ import {
16
16
  cssModuleStaticExpressionAccess,
17
17
  identifierOccurrences
18
18
  } from './projectSymbolGraphCssModuleMemberAccess.js';
19
+ import {
20
+ cssModuleClassHelperCalleeEvidence,
21
+ cssModuleClassHelperCallProof,
22
+ cssModuleHelperArgumentIsConditional,
23
+ cssModuleMemberWriteOperation
24
+ } from './projectSymbolGraphCssModuleClassHelpers.js';
19
25
 
20
26
  function cssModuleLexicalUseSites(importBindings, sourceTextsByPath) {
21
27
  const useSites = [];
@@ -43,7 +49,7 @@ function cssModuleMemberUseSites(binding, sourceText, blockers) {
43
49
  blockers.push(cssModuleAccessBlocker(binding, sourceText, occurrence.start, access.end ?? occurrence.end, access.reasonCode, access.expressionText));
44
50
  continue;
45
51
  }
46
- const writeOperation = memberWriteOperation(sourceText, occurrence.start, access.end);
52
+ const writeOperation = cssModuleMemberWriteOperation(sourceText, occurrence.start, access.end);
47
53
  if (writeOperation) {
48
54
  blockers.push(cssModuleAccessBlocker(binding, sourceText, occurrence.start, access.end, 'css-module-member-write-unsupported', sourceText.slice(occurrence.start, access.end), { writeOperation }));
49
55
  continue;
@@ -93,9 +99,10 @@ function cssModuleDestructuringUseSites(binding, sourceText, blockers) {
93
99
  return useSites;
94
100
  }
95
101
 
96
- function cssModuleJsxUseSites(bindingsByLocal, jsxPropRecords) {
102
+ function cssModuleJsxUseSites(bindingsByLocal, jsxPropRecords, importEdges = []) {
97
103
  const useSites = [];
98
104
  const blockers = [];
105
+ const helperCalleeEvidence = cssModuleClassHelperCalleeEvidence(importEdges);
99
106
  for (const prop of jsxPropRecords ?? []) {
100
107
  const root = prop.propValueReferenceRoot;
101
108
  const sourceBindings = root ? bindingsByLocal.get(localKey(prop.sourcePath, root)) ?? [] : [];
@@ -103,8 +110,8 @@ function cssModuleJsxUseSites(bindingsByLocal, jsxPropRecords) {
103
110
  const classNameBindings = bindingsForSourcePath(bindingsByLocal, prop.sourcePath);
104
111
  if (prop.propName !== 'className' || !classNameBindings.length) continue;
105
112
  useSites.push(...cssModuleJsxStaticComputedUseSites(classNameBindings, prop));
106
- useSites.push(...cssModuleJsxHelperUseSites(classNameBindings, prop));
107
- blockers.push(...cssModuleJsxBlockers(classNameBindings, prop));
113
+ useSites.push(...cssModuleJsxHelperUseSites(classNameBindings, prop, helperCalleeEvidence));
114
+ blockers.push(...cssModuleJsxBlockers(classNameBindings, prop, helperCalleeEvidence));
108
115
  }
109
116
  return { useSites, blockers };
110
117
  }
@@ -148,15 +155,16 @@ function cssModuleJsxStaticComputedUseSites(classNameBindings, prop) {
148
155
  });
149
156
  }
150
157
 
151
- function cssModuleJsxHelperUseSites(classNameBindings, prop) {
158
+ function cssModuleJsxHelperUseSites(classNameBindings, prop, helperCalleeEvidence) {
152
159
  const expressionText = prop.propValueExpressionText ?? prop.propValueDynamicText ?? '';
153
160
  if (!cssModuleExpressionHasCall(expressionText)) return [];
154
161
  const useSites = [];
155
162
  for (const binding of classNameBindings) {
163
+ const helperProof = cssModuleClassHelperCallProof(expressionText, binding, prop, helperCalleeEvidence);
156
164
  for (const occurrence of identifierOccurrences(expressionText, binding.localName)) {
157
165
  const access = cssModuleMemberAccess(expressionText, occurrence.end);
158
166
  if (!access || access.status === 'blocked') continue;
159
- if (memberWriteOperation(expressionText, occurrence.start, access.end)) continue;
167
+ if (cssModuleMemberWriteOperation(expressionText, occurrence.start, access.end)) continue;
160
168
  useSites.push(cssModuleUseSiteRecord(binding, {
161
169
  useSiteKind: 'jsx-className-helper',
162
170
  accessKind: access.accessKind,
@@ -167,14 +175,21 @@ function cssModuleJsxHelperUseSites(classNameBindings, prop) {
167
175
  sourceHash: prop.sourceHash,
168
176
  sourceSpan: prop.sourceSpan,
169
177
  jsxPropRecordId: prop.id,
170
- conditionalRuntimePresence: cssModuleHelperArgumentIsConditional(expressionText, occurrence.start) || undefined
178
+ conditionalRuntimePresence: cssModuleHelperArgumentIsConditional(expressionText, occurrence.start) || undefined,
179
+ helperCallProofLevel: helperProof?.proofLevel,
180
+ helperCallGraphHash: helperProof?.helperCallGraphHash,
181
+ helperCalleeName: helperProof?.helperCalleeName,
182
+ helperCalleeRoot: helperProof?.helperCalleeRoot,
183
+ helperCalleeSource: helperProof?.helperCalleeSource,
184
+ helperModuleSpecifier: helperProof?.helperModuleSpecifier,
185
+ helperImportEdgeId: helperProof?.helperImportEdgeId
171
186
  }));
172
187
  }
173
188
  }
174
189
  return useSites;
175
190
  }
176
191
 
177
- function cssModuleJsxBlockers(classNameBindings, prop) {
192
+ function cssModuleJsxBlockers(classNameBindings, prop, helperCalleeEvidence) {
178
193
  if (prop.propValueKind === 'string') {
179
194
  return classNameBindings.map((binding) => cssModulePropBlocker(binding, prop, 'css-module-string-literal-classname-unproved'));
180
195
  }
@@ -183,43 +198,18 @@ function cssModuleJsxBlockers(classNameBindings, prop) {
183
198
  return classNameBindings.flatMap((binding) => {
184
199
  if (!expressionText.includes(binding.localName)) return [];
185
200
  const reasonCodes = [];
186
- if (cssModuleExpressionHasCall(expressionText)) reasonCodes.push('css-module-helper-call-unproved');
201
+ const helperProof = cssModuleClassHelperCallProof(expressionText, binding, prop, helperCalleeEvidence);
202
+ if (cssModuleExpressionHasCall(expressionText) && !helperProof) reasonCodes.push('css-module-helper-call-unproved');
187
203
  if (cssModuleExpressionHasBlockedAccess(expressionText, binding.localName)) reasonCodes.push('css-module-dynamic-member-access-unproved');
188
204
  if (!reasonCodes.length && cssModuleStaticExpressionAccess(expressionText, binding.localName)) return [];
189
205
  if (!reasonCodes.length && prop.propValueDynamicBlockerReasonCode) {
190
- reasonCodes.push(jsxDynamicReason(prop.propValueDynamicBlockerReasonCode));
206
+ const dynamicReason = jsxDynamicReason(prop.propValueDynamicBlockerReasonCode);
207
+ if (dynamicReason !== 'css-module-helper-call-unproved' || !helperProof) reasonCodes.push(dynamicReason);
191
208
  }
192
209
  return [...new Set(reasonCodes)].map((reasonCode) => cssModulePropBlocker(binding, prop, reasonCode));
193
210
  });
194
211
  }
195
212
 
196
- function memberWriteOperation(sourceText, start, end) {
197
- if (hasPrefixUpdateOperator(sourceText, start)) return 'update';
198
- let index = end;
199
- while (index < sourceText.length && /\s/.test(sourceText[index])) index += 1;
200
- if (sourceText.slice(index, index + 2) === '++' || sourceText.slice(index, index + 2) === '--') return 'update';
201
- return assignmentOperatorAt(sourceText, index) ? 'assignment' : undefined;
202
- }
203
-
204
- function assignmentOperatorAt(code, index) {
205
- for (const operator of ['>>>=', '**=', '<<=', '>>=', '&&=', '||=', '??=', '+=', '-=', '*=', '/=', '%=', '&=', '|=', '^=', '=']) {
206
- if (!code.startsWith(operator, index)) continue;
207
- if (operator === '=' && (code[index + 1] === '=' || code[index + 1] === '>')) return undefined;
208
- return operator;
209
- }
210
- return undefined;
211
- }
212
-
213
- function hasPrefixUpdateOperator(code, tokenStart) {
214
- let index = tokenStart - 1;
215
- while (index >= 0 && /\s/.test(code[index])) index -= 1;
216
- const operator = code.slice(index - 1, index + 1);
217
- if (operator !== '++' && operator !== '--') return false;
218
- let before = index - 2;
219
- while (before >= 0 && /\s/.test(code[before])) before -= 1;
220
- return before < 0 || !/[A-Za-z0-9_$)\]]/.test(code[before]);
221
- }
222
-
223
213
  function destructuringProperty(text) {
224
214
  const value = String(text ?? '').trim();
225
215
  const [rawName, rawLocal] = value.split(':').map((part) => part.trim());
@@ -249,16 +239,6 @@ function splitTopLevelComma(text, baseOffset) {
249
239
  return parts;
250
240
  }
251
241
 
252
- function cssModuleHelperArgumentIsConditional(expressionText, occurrenceStart) {
253
- const prefix = expressionText.slice(0, occurrenceStart);
254
- const suffix = expressionText.slice(occurrenceStart);
255
- const lastComma = prefix.lastIndexOf(',');
256
- const argumentPrefix = prefix.slice(lastComma + 1);
257
- const nextComma = suffix.indexOf(',');
258
- const argumentText = `${argumentPrefix}${nextComma === -1 ? suffix : suffix.slice(0, nextComma)}`;
259
- return /(?:&&|\|\||\?)/.test(argumentText);
260
- }
261
-
262
242
  function jsxDynamicReason(reasonCode) {
263
243
  if (reasonCode.includes('call')) return 'css-module-helper-call-unproved';
264
244
  if (reasonCode.includes('computed')) return 'css-module-dynamic-member-access-unproved';
@@ -15,6 +15,7 @@ import {
15
15
  cssModuleTransformBlockers,
16
16
  cssModuleUseSiteGraphRecords
17
17
  } from './projectSymbolGraphCssModuleRecords.js';
18
+ import { withProjectCssModuleDependencyGraphs } from './projectSymbolGraphCssModuleDependencyGraphs.js';
18
19
  import {
19
20
  cssModuleJsxUseSites,
20
21
  cssModuleLexicalUseSites
@@ -27,13 +28,14 @@ function createProjectCssModuleGraphRecords(semanticIndex, imports, importEdges,
27
28
  const cssSourcesByPath = new Map(imports
28
29
  .map((imported) => [sourcePathForImport(imported), cssModuleSourceRecord(imported)])
29
30
  .filter(([sourcePath]) => sourcePath));
31
+ const cssSourcesWithDependencyGraphsByPath = withProjectCssModuleDependencyGraphs(cssSourcesByPath);
30
32
  const documentsById = new Map((semanticIndex?.documents ?? []).map((document) => [document.id, document]));
31
33
  const importBindings = uniqueRecords(importEdges
32
34
  .filter(isCssModuleBindingEdge)
33
- .map((edge, index) => cssModuleImportBindingRecord(edge, index, documentsById, cssSourcesByPath)));
35
+ .map((edge, index) => cssModuleImportBindingRecord(edge, index, documentsById, cssSourcesWithDependencyGraphsByPath)));
34
36
  const bindingsByLocal = groupBindingsByLocal(importBindings);
35
37
  const { useSites: lexicalUseSites, blockers: lexicalBlockers } = cssModuleLexicalUseSites(importBindings, sourceTextsByPath);
36
- const { useSites: jsxUseSites, blockers: jsxBlockers } = cssModuleJsxUseSites(bindingsByLocal, jsxPropRecords);
38
+ const { useSites: jsxUseSites, blockers: jsxBlockers } = cssModuleJsxUseSites(bindingsByLocal, jsxPropRecords, importEdges);
37
39
  const namedImportBlockers = importBindings
38
40
  .filter((binding) => binding.importKind === 'named')
39
41
  .map((binding) => cssModuleNamedExportBlocker(binding));
@@ -43,7 +45,7 @@ function createProjectCssModuleGraphRecords(semanticIndex, imports, importEdges,
43
45
  const missingTransformBlockers = usedImportBindings
44
46
  .flatMap((binding) => cssModuleTransformBlockers(binding));
45
47
  const missingDependencyGraphBlockers = usedImportBindings
46
- .flatMap((binding) => cssModuleDependencyGraphBlockers(binding, cssSourcesByPath));
48
+ .flatMap((binding) => cssModuleDependencyGraphBlockers(binding, cssSourcesWithDependencyGraphsByPath));
47
49
  const bindingsById = new Map(importBindings.map((binding) => [binding.id, binding]));
48
50
  const missingExportBlockers = cssModuleMissingExportBlockers(bindingsById, cssModuleUseSites);
49
51
  const cssModuleUseSiteBlockers = uniqueRecords([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shapeshift-labs/frontier-lang-compiler",
3
- "version": "0.2.196",
3
+ "version": "0.2.198",
4
4
  "description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",