@shapeshift-labs/frontier-lang-compiler 0.2.153 → 0.2.155
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.
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { readStaticMemberLiteral } from './staticMemberLiteral.js';
|
|
2
|
+
import { escapeRegExp, isIdentifierChar } from './projectSymbolGraphCssModuleUtils.js';
|
|
3
|
+
|
|
4
|
+
const IdentifierPattern = /^[A-Za-z_$][\w$]*$/;
|
|
5
|
+
|
|
6
|
+
function cssModuleMemberAccess(sourceText, receiverEnd) {
|
|
7
|
+
let index = receiverEnd;
|
|
8
|
+
while (index < sourceText.length && /\s/.test(sourceText[index])) index += 1;
|
|
9
|
+
if (sourceText[index] === '?' && sourceText[index + 1] === '.') index += 2;
|
|
10
|
+
else if (sourceText[index] === '.') index += 1;
|
|
11
|
+
else if (sourceText[index] === '[') return cssModuleComputedAccess(sourceText, index);
|
|
12
|
+
else return undefined;
|
|
13
|
+
while (index < sourceText.length && /\s/.test(sourceText[index])) index += 1;
|
|
14
|
+
const match = /^[A-Za-z_$][\w$]*/.exec(sourceText.slice(index));
|
|
15
|
+
return match ? { accessKind: 'dot', memberName: match[0], end: index + match[0].length } : undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function cssModuleComputedAccess(sourceText, open) {
|
|
19
|
+
const close = findMatchingBracket(sourceText, open);
|
|
20
|
+
if (close === -1) return { status: 'blocked', reasonCode: 'css-module-dynamic-member-access-unproved', end: open + 1 };
|
|
21
|
+
let index = open + 1;
|
|
22
|
+
while (index < close && /\s/.test(sourceText[index])) index += 1;
|
|
23
|
+
const literal = readStaticMemberLiteral(sourceText, index, close);
|
|
24
|
+
if (!literal || !IdentifierPattern.test(literal.value)) {
|
|
25
|
+
return { status: 'blocked', reasonCode: 'css-module-dynamic-member-access-unproved', end: close + 1, expressionText: sourceText.slice(open, close + 1) };
|
|
26
|
+
}
|
|
27
|
+
return { accessKind: literal.literalKind === 'static-template-literal' ? 'static-template' : 'static-bracket', memberName: literal.value, end: close + 1 };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function cssModuleStaticExpressionAccess(expressionText, localName) {
|
|
31
|
+
const value = String(expressionText ?? '').trim();
|
|
32
|
+
if (!value.startsWith(localName)) return undefined;
|
|
33
|
+
if (isIdentifierChar(value[localName.length])) return undefined;
|
|
34
|
+
const access = cssModuleMemberAccess(value, localName.length);
|
|
35
|
+
if (!access || access.status === 'blocked' || access.end !== value.length) return undefined;
|
|
36
|
+
return {
|
|
37
|
+
accessKind: access.accessKind,
|
|
38
|
+
memberName: access.memberName,
|
|
39
|
+
optional: /\?\./.test(value.slice(0, access.end))
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function cssModuleExpressionHasCall(expressionText) {
|
|
44
|
+
const value = String(expressionText ?? '');
|
|
45
|
+
return /\?\.\s*\(/.test(value) || /\b[A-Za-z_$][\w$]*(?:\s*(?:\.|\?\.)\s*[A-Za-z_$][\w$]*)*\s*\(/.test(value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function cssModuleExpressionHasBlockedAccess(expressionText, localName) {
|
|
49
|
+
const value = String(expressionText ?? '');
|
|
50
|
+
for (const occurrence of identifierOccurrences(value, localName)) {
|
|
51
|
+
const access = cssModuleMemberAccess(value, occurrence.end);
|
|
52
|
+
if (access?.status === 'blocked') return true;
|
|
53
|
+
}
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function identifierOccurrences(sourceText, name) {
|
|
58
|
+
const result = [];
|
|
59
|
+
const pattern = new RegExp(`\\b${escapeRegExp(name)}\\b`, 'g');
|
|
60
|
+
for (const match of sourceText.matchAll(pattern)) {
|
|
61
|
+
const start = match.index;
|
|
62
|
+
const end = start + name.length;
|
|
63
|
+
if (isIdentifierChar(sourceText[start - 1]) || isIdentifierChar(sourceText[end])) continue;
|
|
64
|
+
result.push({ start, end });
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function findMatchingBracket(sourceText, open) {
|
|
70
|
+
let depth = 0;
|
|
71
|
+
for (let index = open; index < sourceText.length; index += 1) {
|
|
72
|
+
if (sourceText[index] === '[') depth += 1;
|
|
73
|
+
else if (sourceText[index] === ']' && --depth === 0) return index;
|
|
74
|
+
}
|
|
75
|
+
return -1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export {
|
|
79
|
+
cssModuleExpressionHasBlockedAccess,
|
|
80
|
+
cssModuleExpressionHasCall,
|
|
81
|
+
cssModuleMemberAccess,
|
|
82
|
+
cssModuleStaticExpressionAccess,
|
|
83
|
+
identifierOccurrences
|
|
84
|
+
};
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
import { readStaticMemberLiteral } from './staticMemberLiteral.js';
|
|
2
1
|
import {
|
|
3
2
|
bindingsForSourcePath,
|
|
4
3
|
escapeRegExp,
|
|
5
|
-
isIdentifierChar,
|
|
6
4
|
localKey,
|
|
7
5
|
sourceSpanForRange
|
|
8
6
|
} from './projectSymbolGraphCssModuleUtils.js';
|
|
@@ -11,8 +9,13 @@ import {
|
|
|
11
9
|
cssModulePropBlocker,
|
|
12
10
|
cssModuleUseSiteRecord
|
|
13
11
|
} from './projectSymbolGraphCssModuleRecords.js';
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
import {
|
|
13
|
+
cssModuleExpressionHasBlockedAccess,
|
|
14
|
+
cssModuleExpressionHasCall,
|
|
15
|
+
cssModuleMemberAccess,
|
|
16
|
+
cssModuleStaticExpressionAccess,
|
|
17
|
+
identifierOccurrences
|
|
18
|
+
} from './projectSymbolGraphCssModuleMemberAccess.js';
|
|
16
19
|
|
|
17
20
|
function cssModuleLexicalUseSites(importBindings, sourceTextsByPath) {
|
|
18
21
|
const useSites = [];
|
|
@@ -99,13 +102,15 @@ function cssModuleJsxUseSites(bindingsByLocal, jsxPropRecords) {
|
|
|
99
102
|
useSites.push(...cssModuleJsxStaticUseSites(sourceBindings, prop));
|
|
100
103
|
const classNameBindings = bindingsForSourcePath(bindingsByLocal, prop.sourcePath);
|
|
101
104
|
if (prop.propName !== 'className' || !classNameBindings.length) continue;
|
|
105
|
+
useSites.push(...cssModuleJsxStaticComputedUseSites(classNameBindings, prop));
|
|
106
|
+
useSites.push(...cssModuleJsxHelperUseSites(classNameBindings, prop));
|
|
102
107
|
blockers.push(...cssModuleJsxBlockers(classNameBindings, prop));
|
|
103
108
|
}
|
|
104
109
|
return { useSites, blockers };
|
|
105
110
|
}
|
|
106
111
|
|
|
107
112
|
function cssModuleJsxStaticUseSites(sourceBindings, prop) {
|
|
108
|
-
if (prop.propName !== 'className' || prop.propValueKind !== 'reference') return [];
|
|
113
|
+
if (prop.propName !== 'className' || (prop.propValueKind !== 'reference' && prop.propValueKind !== 'optional-reference')) return [];
|
|
109
114
|
const referencePath = prop.propValueReferencePath ?? [];
|
|
110
115
|
if (referencePath.length < 2) return [];
|
|
111
116
|
return sourceBindings.map((binding) => cssModuleUseSiteRecord(binding, {
|
|
@@ -117,43 +122,75 @@ function cssModuleJsxStaticUseSites(sourceBindings, prop) {
|
|
|
117
122
|
sourcePath: prop.sourcePath,
|
|
118
123
|
sourceHash: prop.sourceHash,
|
|
119
124
|
sourceSpan: prop.sourceSpan,
|
|
120
|
-
jsxPropRecordId: prop.id
|
|
125
|
+
jsxPropRecordId: prop.id,
|
|
126
|
+
conditionalRuntimePresence: prop.propValueKind === 'optional-reference' || undefined
|
|
121
127
|
}));
|
|
122
128
|
}
|
|
123
129
|
|
|
124
|
-
function
|
|
125
|
-
if (prop.propValueKind === '
|
|
126
|
-
return classNameBindings.map((binding) => cssModulePropBlocker(binding, prop, 'css-module-string-literal-classname-unproved'));
|
|
127
|
-
}
|
|
128
|
-
if (!prop.propValueDynamicBlockerReasonCode) return [];
|
|
130
|
+
function cssModuleJsxStaticComputedUseSites(classNameBindings, prop) {
|
|
131
|
+
if (prop.propValueKind === 'reference' || prop.propValueKind === 'optional-reference') return [];
|
|
129
132
|
const expressionText = prop.propValueExpressionText ?? prop.propValueDynamicText ?? '';
|
|
130
|
-
return classNameBindings
|
|
131
|
-
|
|
132
|
-
|
|
133
|
+
return classNameBindings.flatMap((binding) => {
|
|
134
|
+
const access = cssModuleStaticExpressionAccess(expressionText, binding.localName);
|
|
135
|
+
if (!access) return [];
|
|
136
|
+
return [cssModuleUseSiteRecord(binding, {
|
|
137
|
+
useSiteKind: 'jsx-className',
|
|
138
|
+
accessKind: access.accessKind,
|
|
139
|
+
exportName: access.memberName,
|
|
140
|
+
receiverLocalName: binding.localName,
|
|
141
|
+
expressionText,
|
|
142
|
+
sourcePath: prop.sourcePath,
|
|
143
|
+
sourceHash: prop.sourceHash,
|
|
144
|
+
sourceSpan: prop.sourceSpan,
|
|
145
|
+
jsxPropRecordId: prop.id,
|
|
146
|
+
conditionalRuntimePresence: access.optional || undefined
|
|
147
|
+
})];
|
|
148
|
+
});
|
|
133
149
|
}
|
|
134
150
|
|
|
135
|
-
function
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
151
|
+
function cssModuleJsxHelperUseSites(classNameBindings, prop) {
|
|
152
|
+
const expressionText = prop.propValueExpressionText ?? prop.propValueDynamicText ?? '';
|
|
153
|
+
if (!cssModuleExpressionHasCall(expressionText)) return [];
|
|
154
|
+
const useSites = [];
|
|
155
|
+
for (const binding of classNameBindings) {
|
|
156
|
+
for (const occurrence of identifierOccurrences(expressionText, binding.localName)) {
|
|
157
|
+
const access = cssModuleMemberAccess(expressionText, occurrence.end);
|
|
158
|
+
if (!access || access.status === 'blocked') continue;
|
|
159
|
+
if (memberWriteOperation(expressionText, occurrence.start, access.end)) continue;
|
|
160
|
+
useSites.push(cssModuleUseSiteRecord(binding, {
|
|
161
|
+
useSiteKind: 'jsx-className-helper',
|
|
162
|
+
accessKind: access.accessKind,
|
|
163
|
+
exportName: access.memberName,
|
|
164
|
+
receiverLocalName: binding.localName,
|
|
165
|
+
expressionText: expressionText.slice(occurrence.start, access.end),
|
|
166
|
+
sourcePath: prop.sourcePath,
|
|
167
|
+
sourceHash: prop.sourceHash,
|
|
168
|
+
sourceSpan: prop.sourceSpan,
|
|
169
|
+
jsxPropRecordId: prop.id,
|
|
170
|
+
conditionalRuntimePresence: cssModuleHelperArgumentIsConditional(expressionText, occurrence.start) || undefined
|
|
171
|
+
}));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return useSites;
|
|
145
175
|
}
|
|
146
176
|
|
|
147
|
-
function
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
let index = open + 1;
|
|
151
|
-
while (index < close && /\s/.test(sourceText[index])) index += 1;
|
|
152
|
-
const literal = readStaticMemberLiteral(sourceText, index, close);
|
|
153
|
-
if (!literal || !IdentifierPattern.test(literal.value)) {
|
|
154
|
-
return { status: 'blocked', reasonCode: 'css-module-dynamic-member-access-unproved', end: close + 1, expressionText: sourceText.slice(open, close + 1) };
|
|
177
|
+
function cssModuleJsxBlockers(classNameBindings, prop) {
|
|
178
|
+
if (prop.propValueKind === 'string') {
|
|
179
|
+
return classNameBindings.map((binding) => cssModulePropBlocker(binding, prop, 'css-module-string-literal-classname-unproved'));
|
|
155
180
|
}
|
|
156
|
-
|
|
181
|
+
const expressionText = prop.propValueExpressionText ?? prop.propValueDynamicText ?? '';
|
|
182
|
+
if (!expressionText) return [];
|
|
183
|
+
return classNameBindings.flatMap((binding) => {
|
|
184
|
+
if (!expressionText.includes(binding.localName)) return [];
|
|
185
|
+
const reasonCodes = [];
|
|
186
|
+
if (cssModuleExpressionHasCall(expressionText)) reasonCodes.push('css-module-helper-call-unproved');
|
|
187
|
+
if (cssModuleExpressionHasBlockedAccess(expressionText, binding.localName)) reasonCodes.push('css-module-dynamic-member-access-unproved');
|
|
188
|
+
if (!reasonCodes.length && cssModuleStaticExpressionAccess(expressionText, binding.localName)) return [];
|
|
189
|
+
if (!reasonCodes.length && prop.propValueDynamicBlockerReasonCode) {
|
|
190
|
+
reasonCodes.push(jsxDynamicReason(prop.propValueDynamicBlockerReasonCode));
|
|
191
|
+
}
|
|
192
|
+
return [...new Set(reasonCodes)].map((reasonCode) => cssModulePropBlocker(binding, prop, reasonCode));
|
|
193
|
+
});
|
|
157
194
|
}
|
|
158
195
|
|
|
159
196
|
function memberWriteOperation(sourceText, start, end) {
|
|
@@ -183,15 +220,6 @@ function hasPrefixUpdateOperator(code, tokenStart) {
|
|
|
183
220
|
return before < 0 || !/[A-Za-z0-9_$)\]]/.test(code[before]);
|
|
184
221
|
}
|
|
185
222
|
|
|
186
|
-
function findMatchingBracket(sourceText, open) {
|
|
187
|
-
let depth = 0;
|
|
188
|
-
for (let index = open; index < sourceText.length; index += 1) {
|
|
189
|
-
if (sourceText[index] === '[') depth += 1;
|
|
190
|
-
else if (sourceText[index] === ']' && --depth === 0) return index;
|
|
191
|
-
}
|
|
192
|
-
return -1;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
223
|
function destructuringProperty(text) {
|
|
196
224
|
const value = String(text ?? '').trim();
|
|
197
225
|
const [rawName, rawLocal] = value.split(':').map((part) => part.trim());
|
|
@@ -221,16 +249,14 @@ function splitTopLevelComma(text, baseOffset) {
|
|
|
221
249
|
return parts;
|
|
222
250
|
}
|
|
223
251
|
|
|
224
|
-
function
|
|
225
|
-
const
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
return result;
|
|
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);
|
|
234
260
|
}
|
|
235
261
|
|
|
236
262
|
function jsxDynamicReason(reasonCode) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shapeshift-labs/frontier-lang-compiler",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.155",
|
|
4
4
|
"description": "Compiler facade for Frontier Lang source documents and language projection adapters.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"dependencies": {
|
|
64
64
|
"@shapeshift-labs/frontier-lang-c": "0.2.9",
|
|
65
65
|
"@shapeshift-labs/frontier-lang-checker": "0.3.8",
|
|
66
|
-
"@shapeshift-labs/frontier-lang-css": "^0.1.
|
|
66
|
+
"@shapeshift-labs/frontier-lang-css": "^0.1.5",
|
|
67
67
|
"@shapeshift-labs/frontier-lang-javascript": "0.2.9",
|
|
68
68
|
"@shapeshift-labs/frontier-lang-kernel": "0.3.12",
|
|
69
69
|
"@shapeshift-labs/frontier-lang-parser": "0.3.8",
|