modality-ts 0.0.6 → 0.0.8
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/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +52 -23
- package/dist/extraction/index.js.map +1 -1
- package/dist/kernel/ir/validator.js +35 -3
- package/dist/kernel/ir/validator.js.map +1 -1
- package/dist/sources/jotai/index.d.ts +1 -1
- package/dist/sources/jotai/index.d.ts.map +1 -1
- package/dist/sources/jotai/index.js +10 -6
- package/dist/sources/jotai/index.js.map +1 -1
- package/dist/sources/use-state/index.d.ts +1 -1
- package/dist/sources/use-state/index.d.ts.map +1 -1
- package/dist/sources/use-state/index.js +10 -6
- package/dist/sources/use-state/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/extraction/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/extraction/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,EAA4C,KAAK,cAAc,EAA4C,KAAK,YAAY,EAAE,KAAK,UAAU,EAAE,KAAK,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC7L,OAAO,KAAK,EAAmB,YAAY,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAErG,cAAc,qBAAqB,CAAC;AACpC,cAAc,gBAAgB,CAAC;AAE/B,MAAM,WAAW,yBAAyB;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,aAAa,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,KAAK,CAAC;QAAC,KAAK,CAAC,EAAE,KAAK,CAAA;KAAE,CAAC,CAAC;IAClE,SAAS,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IACpC,aAAa,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IACxC,aAAa,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC7C,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,YAAY,EAAE,CAAC;IACrB,QAAQ,EAAE,iBAAiB,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,sBAAuB,SAAQ,wBAAwB;IACtE,WAAW,EAAE,UAAU,EAAE,CAAC;CAC3B;AA6OD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,GAAG,SAAS,EAAE,WAAW,GAAE,WAAW,CAAC,MAAM,EAAE,EAAE,CAAC,QAAQ,CAAa,GAAG,cAAc,CAuBhJ;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,yBAA8B,GAAG,wBAAwB,CAEzH;AAED,wBAAgB,uBAAuB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,GAAE,yBAA8B,GAAG,sBAAsB,CA4J3H"}
|
package/dist/extraction/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as ts from "typescript";
|
|
2
|
-
import { effectReads, effectWrites } from "modality-ts/kernel";
|
|
2
|
+
import { effectReads, effectWrites, validateValue } from "modality-ts/kernel";
|
|
3
3
|
export * from "./pipeline/index.js";
|
|
4
4
|
export * from "./spi/index.js";
|
|
5
5
|
function emptyContextBindings() {
|
|
@@ -15,6 +15,29 @@ function setterBindingFromDecl(decl) {
|
|
|
15
15
|
domain: decl.domain
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
|
+
function bindSetter(setters, symbolName, setter) {
|
|
19
|
+
setters.set(scopedSetterKey(setter.component, symbolName), setter);
|
|
20
|
+
const current = setters.get(symbolName);
|
|
21
|
+
if (!current || current.varId === setter.varId) {
|
|
22
|
+
setters.set(symbolName, setter);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
setters.delete(symbolName);
|
|
26
|
+
}
|
|
27
|
+
function scopedSetterKey(component, symbolName) {
|
|
28
|
+
return `${component}:${symbolName}`;
|
|
29
|
+
}
|
|
30
|
+
function settersForComponent(setters, component) {
|
|
31
|
+
if (!component)
|
|
32
|
+
return new Map(setters);
|
|
33
|
+
const scoped = new Map(setters);
|
|
34
|
+
for (const [key, setter] of setters) {
|
|
35
|
+
if (!key.startsWith(`${component}:`))
|
|
36
|
+
continue;
|
|
37
|
+
scoped.set(key.slice(component.length + 1), setter);
|
|
38
|
+
}
|
|
39
|
+
return scoped;
|
|
40
|
+
}
|
|
18
41
|
function discoverContextBindings(source, fileName, route, typeAliases) {
|
|
19
42
|
const bindings = emptyContextBindings();
|
|
20
43
|
const providerValues = new Map();
|
|
@@ -246,7 +269,7 @@ export function extractUseStateSkeleton(sourceText, options = {}) {
|
|
|
246
269
|
const decl = options.stateVars.find((candidate) => candidate.id === channel.varId);
|
|
247
270
|
if (!decl)
|
|
248
271
|
continue;
|
|
249
|
-
setters
|
|
272
|
+
bindSetter(setters, channel.symbolName, setterBindingFromDecl(decl));
|
|
250
273
|
}
|
|
251
274
|
}
|
|
252
275
|
for (const decl of contextBindings.vars) {
|
|
@@ -309,7 +332,7 @@ export function extractUseStateSkeleton(sourceText, options = {}) {
|
|
|
309
332
|
}
|
|
310
333
|
if (setterName && ts.isBindingElement(setterName) && ts.isIdentifier(setterName.name)) {
|
|
311
334
|
if (!options.writeChannels)
|
|
312
|
-
setters
|
|
335
|
+
bindSetter(setters, setterName.name.text, { varId, component, stateName: stateName.name.text, domain });
|
|
313
336
|
}
|
|
314
337
|
}
|
|
315
338
|
else {
|
|
@@ -319,7 +342,8 @@ export function extractUseStateSkeleton(sourceText, options = {}) {
|
|
|
319
342
|
const link = linkNavigationTransition(source, fileName, node, nextComponent ?? "Anonymous", routePatterns);
|
|
320
343
|
if (link)
|
|
321
344
|
transitions.push(link);
|
|
322
|
-
const
|
|
345
|
+
const scopedSetters = settersForComponent(setters, nextComponent);
|
|
346
|
+
const refTaint = refSetterTaint(node, scopedSetters);
|
|
323
347
|
if (refTaint) {
|
|
324
348
|
const key = `Global taint ${refTaint.varId}`;
|
|
325
349
|
if (!globalTaints.has(key)) {
|
|
@@ -327,8 +351,8 @@ export function extractUseStateSkeleton(sourceText, options = {}) {
|
|
|
327
351
|
warnings.push({ message: key, ...lineAndColumn(source, refTaint.node) });
|
|
328
352
|
}
|
|
329
353
|
}
|
|
330
|
-
transitions.push(...transitionsFromTimerCall(source, fileName, node,
|
|
331
|
-
for (const timerTaint of timerSetterTaints(node,
|
|
354
|
+
transitions.push(...transitionsFromTimerCall(source, fileName, node, scopedSetters, nextComponent ?? "Anonymous"));
|
|
355
|
+
for (const timerTaint of timerSetterTaints(node, scopedSetters)) {
|
|
332
356
|
const key = `Global taint ${timerTaint.varId}`;
|
|
333
357
|
if (!globalTaints.has(key)) {
|
|
334
358
|
globalTaints.add(key);
|
|
@@ -336,7 +360,7 @@ export function extractUseStateSkeleton(sourceText, options = {}) {
|
|
|
336
360
|
}
|
|
337
361
|
}
|
|
338
362
|
if (ts.isJsxAttribute(node) && ts.isIdentifier(node.name) && node.initializer && isForwardablePropName(node.name.text) && !isIntrinsicJsxAttribute(node)) {
|
|
339
|
-
const extracted = transitionsFromComponentPropAttribute(source, fileName, node,
|
|
363
|
+
const extracted = transitionsFromComponentPropAttribute(source, fileName, node, scopedSetters, handlers, components, nextComponent ?? "Anonymous", effectApis, options.asyncOutcomes ?? {}, sourcePlugins, routerPlugin, warnings);
|
|
340
364
|
transitions.push(...extracted);
|
|
341
365
|
if (extracted.length === 0) {
|
|
342
366
|
warnings.push({ message: `Unextractable handler ${nextComponent ?? "Anonymous"}.${node.name.text}`, ...lineAndColumn(source, node) });
|
|
@@ -346,7 +370,7 @@ export function extractUseStateSkeleton(sourceText, options = {}) {
|
|
|
346
370
|
const listInfo = listRenderedHandlerInfo(node, vars, nextComponent ?? "Anonymous");
|
|
347
371
|
if (listInfo) {
|
|
348
372
|
if (listInfo.domain.kind === "boundedList") {
|
|
349
|
-
const extracted = transitionsFromBoundedListAttribute(source, fileName, node,
|
|
373
|
+
const extracted = transitionsFromBoundedListAttribute(source, fileName, node, scopedSetters, handlers, nextComponent ?? "Anonymous", {
|
|
350
374
|
varId: listInfo.varId,
|
|
351
375
|
domain: listInfo.domain,
|
|
352
376
|
itemName: listInfo.itemName
|
|
@@ -361,21 +385,21 @@ export function extractUseStateSkeleton(sourceText, options = {}) {
|
|
|
361
385
|
ts.forEachChild(node, (child) => visit(child, nextComponent));
|
|
362
386
|
return;
|
|
363
387
|
}
|
|
364
|
-
const guardLocals = componentGuardLocalsFor(node,
|
|
388
|
+
const guardLocals = componentGuardLocalsFor(node, scopedSetters);
|
|
365
389
|
const guard = combineParsedGuards([
|
|
366
|
-
renderGuardFor(node,
|
|
367
|
-
disabledGuardFor(node,
|
|
390
|
+
renderGuardFor(node, scopedSetters, warnings, source, nextComponent ?? "Anonymous", guardLocals),
|
|
391
|
+
disabledGuardFor(node, scopedSetters, warnings, source, nextComponent ?? "Anonymous", guardLocals)
|
|
368
392
|
]);
|
|
369
|
-
const extracted = transitionsFromJsxAttribute(source, fileName, node,
|
|
393
|
+
const extracted = transitionsFromJsxAttribute(source, fileName, node, scopedSetters, handlers, nextComponent ?? "Anonymous", effectApis, options.asyncOutcomes ?? {}, sourcePlugins, routerPlugin, guard, routePatterns, contextBindings, warnings);
|
|
370
394
|
transitions.push(...extracted);
|
|
371
|
-
if (extracted.length === 0 && !forwardsComponentProp(node, handlers, components.get(nextComponent ?? "")) && !handlerSchedulesModeledTimer(node, handlers,
|
|
395
|
+
if (extracted.length === 0 && !forwardsComponentProp(node, handlers, components.get(nextComponent ?? "")) && !handlerSchedulesModeledTimer(node, handlers, scopedSetters)) {
|
|
372
396
|
warnings.push({ message: `Unextractable handler ${nextComponent ?? "Anonymous"}.${node.name.text}`, ...lineAndColumn(source, node) });
|
|
373
397
|
}
|
|
374
398
|
}
|
|
375
399
|
if (ts.isCallExpression(node) && isUseEffectCall(node)) {
|
|
376
|
-
const extracted = transitionsFromUseEffect(source, fileName, node,
|
|
400
|
+
const extracted = transitionsFromUseEffect(source, fileName, node, scopedSetters, nextComponent ?? "Anonymous");
|
|
377
401
|
transitions.push(...extracted);
|
|
378
|
-
if (extracted.length === 0 && useEffectWritesModeledState(node,
|
|
402
|
+
if (extracted.length === 0 && useEffectWritesModeledState(node, scopedSetters) && !providerComponents.has(nextComponent ?? "")) {
|
|
379
403
|
warnings.push({ message: `Unextractable effect ${nextComponent ?? "Anonymous"}.useEffect`, ...lineAndColumn(source, node) });
|
|
380
404
|
}
|
|
381
405
|
}
|
|
@@ -451,23 +475,23 @@ function initialValueForUseState(call, domain) {
|
|
|
451
475
|
if (parsed !== undefined)
|
|
452
476
|
return parsed;
|
|
453
477
|
if (initial.kind === ts.SyntaxKind.TrueKeyword)
|
|
454
|
-
return true;
|
|
478
|
+
return validInitialOrFirst(domain, true);
|
|
455
479
|
if (initial.kind === ts.SyntaxKind.FalseKeyword)
|
|
456
|
-
return false;
|
|
480
|
+
return validInitialOrFirst(domain, false);
|
|
457
481
|
if (ts.isStringLiteral(initial))
|
|
458
|
-
return initial.text;
|
|
482
|
+
return validInitialOrFirst(domain, initial.text);
|
|
459
483
|
if (ts.isNumericLiteral(initial))
|
|
460
|
-
return Number(initial.text);
|
|
484
|
+
return validInitialOrFirst(domain, Number(initial.text));
|
|
461
485
|
if (initial.kind === ts.SyntaxKind.NullKeyword)
|
|
462
|
-
return null;
|
|
486
|
+
return validInitialOrFirst(domain, null);
|
|
463
487
|
if (ts.isArrayLiteralExpression(initial))
|
|
464
|
-
return initial.elements.length === 0 ? "0" : initial.elements.length === 1 ? "1" : "many";
|
|
488
|
+
return validInitialOrFirst(domain, initial.elements.length === 0 ? "0" : initial.elements.length === 1 ? "1" : "many");
|
|
465
489
|
return firstValue(domain);
|
|
466
490
|
}
|
|
467
491
|
function initialValueFromExpression(expression, domain) {
|
|
468
492
|
const literal = literalValue(expression);
|
|
469
493
|
if (literal !== undefined)
|
|
470
|
-
return literal;
|
|
494
|
+
return validateValue(domain, literal) ? literal : undefined;
|
|
471
495
|
if (domain.kind === "option")
|
|
472
496
|
return initialValueFromExpression(expression, domain.inner);
|
|
473
497
|
if (domain.kind === "record" && ts.isObjectLiteralExpression(expression)) {
|
|
@@ -480,6 +504,9 @@ function initialValueFromExpression(expression, domain) {
|
|
|
480
504
|
}
|
|
481
505
|
return undefined;
|
|
482
506
|
}
|
|
507
|
+
function validInitialOrFirst(domain, value) {
|
|
508
|
+
return validateValue(domain, value) ? value : firstValue(domain);
|
|
509
|
+
}
|
|
483
510
|
function domainFromLiteralType(node) {
|
|
484
511
|
const lit = node.literal;
|
|
485
512
|
if (lit.kind === ts.SyntaxKind.TrueKeyword || lit.kind === ts.SyntaxKind.FalseKeyword)
|
|
@@ -2285,7 +2312,9 @@ function transitionsFromUseEffect(source, fileName, node, setters, component) {
|
|
|
2285
2312
|
const effectReads = uniqueStrings(summaries.flatMap((summary) => summary.reads));
|
|
2286
2313
|
const deps = dependencyReads(node.arguments[1], setters, effectReads);
|
|
2287
2314
|
const effects = summaries.map((summary) => summary.effect);
|
|
2288
|
-
|
|
2315
|
+
const hasExplicitDependencyArray = Boolean(node.arguments[1] && ts.isArrayLiteralExpression(node.arguments[1]));
|
|
2316
|
+
const hasUntriggeredHavocEffect = hasExplicitDependencyArray && deps.length === 0 && effects.some((effect) => effect.kind === "havoc");
|
|
2317
|
+
if (effects.length > 0 && !hasUntriggeredHavocEffect) {
|
|
2289
2318
|
const assignEffects = effects.filter((effect) => effect.kind === "assign");
|
|
2290
2319
|
const guards = assignEffects.map((effect) => ({ kind: "neq", args: [{ kind: "read", var: effect.var }, effect.expr] }));
|
|
2291
2320
|
const guard = guards.length > 0 ? guards.slice(1).reduce((acc, next) => andGuard(acc, next), guards[0]) : { kind: "lit", value: true };
|