@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 +2 -1
- package/dist/declarations/native-project-css-modules.d.ts +16 -0
- package/dist/internal/index-impl/projectSymbolGraphCssModuleClassHelpers.js +219 -0
- package/dist/internal/index-impl/projectSymbolGraphCssModuleDependencyGraphs.js +255 -0
- package/dist/internal/index-impl/projectSymbolGraphCssModuleGraphHashes.js +89 -0
- package/dist/internal/index-impl/projectSymbolGraphCssModuleRecords.js +26 -59
- package/dist/internal/index-impl/projectSymbolGraphCssModuleScanners.js +27 -47
- package/dist/internal/index-impl/projectSymbolGraphCssModules.js +5 -3
- package/package.json +1 -1
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,
|
|
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
|
|
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 =
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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