modality-ts 0.0.1 → 0.0.3
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 +1 -1
- package/dist/extraction/index.d.ts.map +1 -1
- package/dist/extraction/index.js +256 -55
- package/dist/extraction/index.js.map +1 -1
- package/dist/extraction/pipeline/index.js +1 -1
- package/dist/extraction/pipeline/index.js.map +1 -1
- package/dist/modality/features/extract/command.d.ts.map +1 -1
- package/dist/modality/features/extract/command.js +103 -3
- package/dist/modality/features/extract/command.js.map +1 -1
- package/dist/sources/jotai/index.d.ts.map +1 -1
- package/dist/sources/jotai/index.js +32 -9
- package/dist/sources/jotai/index.js.map +1 -1
- package/dist/sources/swr/index.d.ts +2 -1
- package/dist/sources/swr/index.d.ts.map +1 -1
- package/dist/sources/swr/index.js +72 -9
- package/dist/sources/swr/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 +23 -9
- package/dist/sources/use-state/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -25,7 +25,7 @@ export interface UseStateExtractionResult {
|
|
|
25
25
|
export interface ExtractedModelSkeleton extends UseStateExtractionResult {
|
|
26
26
|
transitions: Transition[];
|
|
27
27
|
}
|
|
28
|
-
export declare function inferDomainFromTypeNode(node: ts.TypeNode | undefined): AbstractDomain;
|
|
28
|
+
export declare function inferDomainFromTypeNode(node: ts.TypeNode | undefined, typeAliases?: ReadonlyMap<string, ts.TypeNode>): AbstractDomain;
|
|
29
29
|
export declare function extractUseStateVars(sourceText: string, options?: UseStateExtractionOptions): UseStateExtractionResult;
|
|
30
30
|
export declare function extractUseStateSkeleton(sourceText: string, options?: UseStateExtractionOptions): ExtractedModelSkeleton;
|
|
31
31
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -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,KAAK,EAAE,cAAc,EAA6B,YAAY,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AACrH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,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,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;CACzC;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;AA0CD,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,EAAE,CAAC,QAAQ,GAAG,SAAS,GAAG,cAAc,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/extraction/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,YAAY,CAAC;AACjC,OAAO,KAAK,EAAE,cAAc,EAA6B,YAAY,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AACrH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAEnD,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,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;CACzC;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;AA0CD,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,CA4I3H"}
|
package/dist/extraction/index.js
CHANGED
|
@@ -11,7 +11,7 @@ function setterBindingFromDecl(decl) {
|
|
|
11
11
|
domain: decl.domain
|
|
12
12
|
};
|
|
13
13
|
}
|
|
14
|
-
export function inferDomainFromTypeNode(node) {
|
|
14
|
+
export function inferDomainFromTypeNode(node, typeAliases = new Map()) {
|
|
15
15
|
if (!node)
|
|
16
16
|
return { kind: "tokens", count: 1 };
|
|
17
17
|
switch (node.kind) {
|
|
@@ -25,13 +25,13 @@ export function inferDomainFromTypeNode(node) {
|
|
|
25
25
|
case ts.SyntaxKind.LiteralType:
|
|
26
26
|
return domainFromLiteralType(node);
|
|
27
27
|
case ts.SyntaxKind.UnionType:
|
|
28
|
-
return domainFromUnion(node);
|
|
28
|
+
return domainFromUnion(node, typeAliases);
|
|
29
29
|
case ts.SyntaxKind.TypeLiteral:
|
|
30
30
|
return domainFromTypeLiteral(node);
|
|
31
31
|
case ts.SyntaxKind.ArrayType:
|
|
32
32
|
return { kind: "lengthCat" };
|
|
33
33
|
case ts.SyntaxKind.TypeReference:
|
|
34
|
-
return domainFromTypeReference(node);
|
|
34
|
+
return domainFromTypeReference(node, typeAliases);
|
|
35
35
|
default:
|
|
36
36
|
return { kind: "tokens", count: 1 };
|
|
37
37
|
}
|
|
@@ -42,6 +42,7 @@ export function extractUseStateVars(sourceText, options = {}) {
|
|
|
42
42
|
export function extractUseStateSkeleton(sourceText, options = {}) {
|
|
43
43
|
const fileName = options.fileName ?? "App.tsx";
|
|
44
44
|
const source = ts.createSourceFile(fileName, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
|
|
45
|
+
const typeAliases = typeAliasDeclarations(source);
|
|
45
46
|
const vars = options.stateVars ? [...options.stateVars] : [];
|
|
46
47
|
const transitions = [];
|
|
47
48
|
const warnings = [];
|
|
@@ -67,8 +68,10 @@ export function extractUseStateSkeleton(sourceText, options = {}) {
|
|
|
67
68
|
if (!componentName && isCustomHookDeclaration(node))
|
|
68
69
|
return;
|
|
69
70
|
const nextComponent = componentNameFor(node) ?? componentName;
|
|
70
|
-
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer
|
|
71
|
-
|
|
71
|
+
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
|
|
72
|
+
const handler = extractableHandlerInitializer(node.initializer);
|
|
73
|
+
if (handler)
|
|
74
|
+
handlers.set(node.name.text, handler);
|
|
72
75
|
}
|
|
73
76
|
if (ts.isVariableDeclaration(node) && node.initializer && isUseReducerCall(node.initializer)) {
|
|
74
77
|
warnings.push({ message: `Unsupported useReducer ${nextComponent ?? "Anonymous"}.useReducer`, ...lineAndColumn(source, node) });
|
|
@@ -96,7 +99,7 @@ export function extractUseStateSkeleton(sourceText, options = {}) {
|
|
|
96
99
|
const stateName = node.name.elements[0];
|
|
97
100
|
const setterName = node.name.elements[1];
|
|
98
101
|
if (ts.isBindingElement(stateName) && ts.isIdentifier(stateName.name)) {
|
|
99
|
-
const domain = inferUseStateDomain(node.initializer);
|
|
102
|
+
const domain = inferUseStateDomain(node.initializer, typeAliases);
|
|
100
103
|
const component = nextComponent ?? "Anonymous";
|
|
101
104
|
const varId = `local:${component}.${stateName.name.text}`;
|
|
102
105
|
if (!options.stateVars) {
|
|
@@ -159,9 +162,10 @@ export function extractUseStateSkeleton(sourceText, options = {}) {
|
|
|
159
162
|
ts.forEachChild(node, (child) => visit(child, nextComponent));
|
|
160
163
|
return;
|
|
161
164
|
}
|
|
165
|
+
const guardLocals = componentGuardLocalsFor(node, setters);
|
|
162
166
|
const guard = combineParsedGuards([
|
|
163
|
-
renderGuardFor(node, setters, warnings, source, nextComponent ?? "Anonymous"),
|
|
164
|
-
disabledGuardFor(node, setters, warnings, source, nextComponent ?? "Anonymous")
|
|
167
|
+
renderGuardFor(node, setters, warnings, source, nextComponent ?? "Anonymous", guardLocals),
|
|
168
|
+
disabledGuardFor(node, setters, warnings, source, nextComponent ?? "Anonymous", guardLocals)
|
|
165
169
|
]);
|
|
166
170
|
const extracted = transitionsFromJsxAttribute(source, fileName, node, setters, handlers, nextComponent ?? "Anonymous", effectApis, options.asyncOutcomes ?? {}, guard, warnings);
|
|
167
171
|
transitions.push(...extracted);
|
|
@@ -223,10 +227,10 @@ function shortHash(value) {
|
|
|
223
227
|
}
|
|
224
228
|
return (hash >>> 0).toString(36).padStart(6, "0").slice(0, 6);
|
|
225
229
|
}
|
|
226
|
-
function inferUseStateDomain(call) {
|
|
230
|
+
function inferUseStateDomain(call, typeAliases = new Map()) {
|
|
227
231
|
const typeArg = call.typeArguments?.[0];
|
|
228
232
|
if (typeArg)
|
|
229
|
-
return inferDomainFromTypeNode(typeArg);
|
|
233
|
+
return inferDomainFromTypeNode(typeArg, typeAliases);
|
|
230
234
|
const initial = call.arguments[0];
|
|
231
235
|
if (!initial)
|
|
232
236
|
return { kind: "tokens", count: 1 };
|
|
@@ -289,10 +293,10 @@ function domainFromLiteralType(node) {
|
|
|
289
293
|
return { kind: "option", inner: { kind: "tokens", count: 1 } };
|
|
290
294
|
return { kind: "tokens", count: 1 };
|
|
291
295
|
}
|
|
292
|
-
function domainFromUnion(node) {
|
|
296
|
+
function domainFromUnion(node, typeAliases = new Map()) {
|
|
293
297
|
const nonNull = node.types.filter((part) => part.kind !== ts.SyntaxKind.UndefinedKeyword && !(ts.isLiteralTypeNode(part) && part.literal.kind === ts.SyntaxKind.NullKeyword));
|
|
294
298
|
if (nonNull.length !== node.types.length && nonNull.length > 0) {
|
|
295
|
-
return { kind: "option", inner: nonNull.length === 1 ? inferDomainFromTypeNode(nonNull[0]) : domainFromUnionMembers(nonNull) };
|
|
299
|
+
return { kind: "option", inner: nonNull.length === 1 ? inferDomainFromTypeNode(nonNull[0], typeAliases) : domainFromUnionMembers(nonNull) };
|
|
296
300
|
}
|
|
297
301
|
return domainFromUnionMembers(node.types);
|
|
298
302
|
}
|
|
@@ -353,14 +357,27 @@ function domainFromTypeLiteral(node, omitField) {
|
|
|
353
357
|
}
|
|
354
358
|
return { kind: "record", fields };
|
|
355
359
|
}
|
|
356
|
-
function domainFromTypeReference(node) {
|
|
360
|
+
function domainFromTypeReference(node, typeAliases = new Map()) {
|
|
357
361
|
const name = node.typeName.getText();
|
|
362
|
+
const alias = typeAliases.get(name);
|
|
363
|
+
if (alias)
|
|
364
|
+
return inferDomainFromTypeNode(alias, typeAliases);
|
|
358
365
|
if ((name === "Array" || name === "ReadonlyArray") && node.typeArguments?.length === 1)
|
|
359
366
|
return { kind: "lengthCat" };
|
|
360
367
|
if (name === "Record")
|
|
361
368
|
return { kind: "tokens", count: 1 };
|
|
362
369
|
return { kind: "tokens", count: 1 };
|
|
363
370
|
}
|
|
371
|
+
function typeAliasDeclarations(source) {
|
|
372
|
+
const aliases = new Map();
|
|
373
|
+
const visit = (node) => {
|
|
374
|
+
if (ts.isTypeAliasDeclaration(node) && ts.isIdentifier(node.name))
|
|
375
|
+
aliases.set(node.name.text, node.type);
|
|
376
|
+
ts.forEachChild(node, visit);
|
|
377
|
+
};
|
|
378
|
+
visit(source);
|
|
379
|
+
return aliases;
|
|
380
|
+
}
|
|
364
381
|
function isUseStateCall(node) {
|
|
365
382
|
return ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "useState";
|
|
366
383
|
}
|
|
@@ -376,6 +393,15 @@ function isUseEffectCall(node) {
|
|
|
376
393
|
function isExtractableHandler(node) {
|
|
377
394
|
return ts.isArrowFunction(node) || ts.isFunctionExpression(node);
|
|
378
395
|
}
|
|
396
|
+
function extractableHandlerInitializer(node) {
|
|
397
|
+
if (isExtractableHandler(node))
|
|
398
|
+
return node;
|
|
399
|
+
if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "useCallback") {
|
|
400
|
+
const callback = node.arguments[0];
|
|
401
|
+
return callback && isExtractableHandler(callback) ? callback : undefined;
|
|
402
|
+
}
|
|
403
|
+
return undefined;
|
|
404
|
+
}
|
|
379
405
|
function refSetterTaint(node, setters) {
|
|
380
406
|
if (ts.isVariableDeclaration(node) && node.initializer && isUseRefCall(node.initializer)) {
|
|
381
407
|
const arg = node.initializer.arguments[0];
|
|
@@ -504,9 +530,10 @@ function transitionsFromComponentPropAttribute(source, fileName, node, setters,
|
|
|
504
530
|
const handler = handlerExpression(expression, handlers);
|
|
505
531
|
if (!handler)
|
|
506
532
|
return [];
|
|
533
|
+
const guardLocals = componentGuardLocalsFor(node, setters);
|
|
507
534
|
const callerGuard = combineParsedGuards([
|
|
508
|
-
renderGuardFor(node, setters, warnings, source, component),
|
|
509
|
-
disabledGuardFor(node, setters, warnings, source, component)
|
|
535
|
+
renderGuardFor(node, setters, warnings, source, component, guardLocals),
|
|
536
|
+
disabledGuardFor(node, setters, warnings, source, component, guardLocals)
|
|
510
537
|
]);
|
|
511
538
|
return tagStableIdKey(transitionsFromResolvedHandler(source, fileName, node, trigger.attr, handler, setters, handlers, component, effectApis, asyncOutcomes, combineParsedGuards([trigger.guard, callerGuard]), trigger.locator, warnings), handler);
|
|
512
539
|
}
|
|
@@ -578,7 +605,7 @@ function transitionsFromResolvedHandler(source, fileName, node, attr, handler, s
|
|
|
578
605
|
const loopTransitions = loopWriteTransitions(source, fileName, node, attr, handler, setters, component, locator);
|
|
579
606
|
if (loopTransitions.length > 0)
|
|
580
607
|
return applyParsedGuard(loopTransitions, disabledGuard);
|
|
581
|
-
const sequentialTransition = sequentialTransitionFromHandler(source, fileName, node, attr, handler, setters, component, locator);
|
|
608
|
+
const sequentialTransition = sequentialTransitionFromHandler(source, fileName, node, attr, handler, setters, handlers, component, locator);
|
|
582
609
|
if (sequentialTransition)
|
|
583
610
|
return applyParsedGuard([sequentialTransition], disabledGuard);
|
|
584
611
|
const summary = callSummaryFromHandler(handler, setters);
|
|
@@ -590,6 +617,9 @@ function transitionsFromResolvedHandler(source, fileName, node, attr, handler, s
|
|
|
590
617
|
const navigation = navigationTransition(source, fileName, node, attr, component, inlinedCall, locator);
|
|
591
618
|
if (navigation)
|
|
592
619
|
return applyParsedGuard([navigation], disabledGuard);
|
|
620
|
+
const swrMutate = swrMutateTransition(source, fileName, node, attr, component, inlinedCall, locator);
|
|
621
|
+
if (swrMutate)
|
|
622
|
+
return applyParsedGuard([swrMutate], disabledGuard);
|
|
593
623
|
const setterCall = setterCallFrom(inlinedCall, setters);
|
|
594
624
|
if (!setterCall) {
|
|
595
625
|
const escaped = escapedSetters(inlinedCall, setters);
|
|
@@ -617,7 +647,7 @@ function transitionsFromResolvedHandler(source, fileName, node, attr, handler, s
|
|
|
617
647
|
confidence: "exact"
|
|
618
648
|
}], disabledGuard);
|
|
619
649
|
}
|
|
620
|
-
function sequentialTransitionFromHandler(source, fileName, node, attr, handler, setters, component, locator) {
|
|
650
|
+
function sequentialTransitionFromHandler(source, fileName, node, attr, handler, setters, handlers, component, locator) {
|
|
621
651
|
if (!ts.isBlock(handler.body))
|
|
622
652
|
return undefined;
|
|
623
653
|
const locals = new Map();
|
|
@@ -625,6 +655,11 @@ function sequentialTransitionFromHandler(source, fileName, node, attr, handler,
|
|
|
625
655
|
for (const statement of handler.body.statements) {
|
|
626
656
|
if (bindConstStatement(statement, setters, locals))
|
|
627
657
|
continue;
|
|
658
|
+
const helper = helperSummariesFromStatement(statement, handlers, setters);
|
|
659
|
+
if (helper) {
|
|
660
|
+
summaries.push(...helper);
|
|
661
|
+
continue;
|
|
662
|
+
}
|
|
628
663
|
const summary = summarizeSetterStatement(statement, setters, locals);
|
|
629
664
|
if (!summary)
|
|
630
665
|
return undefined;
|
|
@@ -646,6 +681,23 @@ function sequentialTransitionFromHandler(source, fileName, node, attr, handler,
|
|
|
646
681
|
confidence: effects.some((effect) => effect.kind === "havoc") ? "over-approx" : "exact"
|
|
647
682
|
};
|
|
648
683
|
}
|
|
684
|
+
function helperSummariesFromStatement(statement, handlers, setters) {
|
|
685
|
+
if (!ts.isExpressionStatement(statement) || !ts.isCallExpression(statement.expression) || !ts.isIdentifier(statement.expression.expression))
|
|
686
|
+
return undefined;
|
|
687
|
+
const helper = handlers.get(statement.expression.expression.text);
|
|
688
|
+
if (!helper || !ts.isBlock(helper.body))
|
|
689
|
+
return undefined;
|
|
690
|
+
const locals = new Map();
|
|
691
|
+
const summaries = [];
|
|
692
|
+
for (const child of helper.body.statements) {
|
|
693
|
+
if (bindConstStatement(child, setters, locals))
|
|
694
|
+
continue;
|
|
695
|
+
const summary = summarizeSetterStatement(child, setters, locals);
|
|
696
|
+
if (summary)
|
|
697
|
+
summaries.push(summary);
|
|
698
|
+
}
|
|
699
|
+
return summaries.length > 0 ? summaries : undefined;
|
|
700
|
+
}
|
|
649
701
|
function loopWriteTransitions(source, fileName, node, attr, handler, setters, component, locator) {
|
|
650
702
|
if (!ts.isBlock(handler.body))
|
|
651
703
|
return [];
|
|
@@ -711,6 +763,11 @@ function havocSetterTransition(source, fileName, node, attr, component, setter,
|
|
|
711
763
|
};
|
|
712
764
|
}
|
|
713
765
|
function setterArgumentExpr(argument, setter, setters, locals) {
|
|
766
|
+
if (ts.isObjectLiteralExpression(argument)) {
|
|
767
|
+
const object = objectLiteralAssignmentExpr(argument, setter.domain, setters, locals);
|
|
768
|
+
if (object)
|
|
769
|
+
return object;
|
|
770
|
+
}
|
|
714
771
|
if ((ts.isArrowFunction(argument) || ts.isFunctionExpression(argument)) && argument.parameters.length === 1 && ts.isIdentifier(argument.parameters[0].name)) {
|
|
715
772
|
if (ts.isBlock(argument.body))
|
|
716
773
|
return undefined;
|
|
@@ -718,11 +775,42 @@ function setterArgumentExpr(argument, setter, setters, locals) {
|
|
|
718
775
|
}
|
|
719
776
|
return valueExpr(argument, setters, locals);
|
|
720
777
|
}
|
|
778
|
+
function objectLiteralAssignmentExpr(expression, domain, setters, locals) {
|
|
779
|
+
const value = {};
|
|
780
|
+
const reads = new Set();
|
|
781
|
+
const fields = domain.kind === "record" ? domain.fields : domain.kind === "tagged" ? taggedFieldsForObject(expression, domain) : {};
|
|
782
|
+
for (const property of expression.properties) {
|
|
783
|
+
if (!ts.isPropertyAssignment(property))
|
|
784
|
+
return undefined;
|
|
785
|
+
const name = propertyName(property.name);
|
|
786
|
+
if (!name)
|
|
787
|
+
return undefined;
|
|
788
|
+
const literal = literalValue(property.initializer);
|
|
789
|
+
if (literal !== undefined) {
|
|
790
|
+
value[name] = literal;
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
const bound = valueExpr(property.initializer, setters, locals);
|
|
794
|
+
if (bound?.expr.kind === "lit") {
|
|
795
|
+
value[name] = bound.expr.value;
|
|
796
|
+
bound.reads.forEach((read) => reads.add(read));
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
value[name] = firstValue(fields[name] ?? { kind: "tokens", count: 1 });
|
|
800
|
+
}
|
|
801
|
+
return { expr: { kind: "lit", value }, reads: [...reads] };
|
|
802
|
+
}
|
|
803
|
+
function taggedFieldsForObject(expression, domain) {
|
|
804
|
+
const tagProperty = expression.properties.find((property) => ts.isPropertyAssignment(property) && propertyName(property.name) === domain.tag);
|
|
805
|
+
const tag = tagProperty ? literalValue(tagProperty.initializer) : undefined;
|
|
806
|
+
const variant = typeof tag === "string" ? domain.variants[tag] : undefined;
|
|
807
|
+
return variant?.kind === "record" ? variant.fields : {};
|
|
808
|
+
}
|
|
721
809
|
function valueExpr(expression, setters, locals) {
|
|
722
810
|
const value = literalValue(expression);
|
|
723
811
|
if (value !== undefined)
|
|
724
812
|
return { expr: { kind: "lit", value }, reads: [] };
|
|
725
|
-
if (ts.isIdentifier(expression) ||
|
|
813
|
+
if (ts.isIdentifier(expression) || isPropertyAccessLike(expression))
|
|
726
814
|
return modeledReadExpr(expression, setters, locals);
|
|
727
815
|
if (ts.isPrefixUnaryExpression(expression) && expression.operator === ts.SyntaxKind.ExclamationToken) {
|
|
728
816
|
const parsed = booleanExpr(expression.operand, setters, locals);
|
|
@@ -731,7 +819,7 @@ function valueExpr(expression, setters, locals) {
|
|
|
731
819
|
if (ts.isParenthesizedExpression(expression))
|
|
732
820
|
return valueExpr(expression.expression, setters, locals);
|
|
733
821
|
if (ts.isBinaryExpression(expression) && expression.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
|
|
734
|
-
return nullishOptionalReadExpr(expression, setters);
|
|
822
|
+
return nullishOptionalReadExpr(expression, setters, locals);
|
|
735
823
|
}
|
|
736
824
|
if (ts.isConditionalExpression(expression)) {
|
|
737
825
|
const condition = booleanExpr(expression.condition, setters, locals);
|
|
@@ -775,22 +863,25 @@ function objectSpreadUpdateExpr(expression, setters, locals) {
|
|
|
775
863
|
}
|
|
776
864
|
return current;
|
|
777
865
|
}
|
|
778
|
-
function nullishOptionalReadExpr(expression, setters) {
|
|
779
|
-
|
|
866
|
+
function nullishOptionalReadExpr(expression, setters, locals = new Map()) {
|
|
867
|
+
const fallback = literalValue(expression.right);
|
|
868
|
+
if (fallback === undefined)
|
|
780
869
|
return undefined;
|
|
781
870
|
const read = optionalReadPath(expression.left);
|
|
782
871
|
if (!read?.optional || read.path.length === 0)
|
|
783
872
|
return undefined;
|
|
784
|
-
const
|
|
873
|
+
const local = locals.get(read.base);
|
|
874
|
+
const varId = local?.expr.kind === "read" ? local.expr.var : stateVarForName(read.base, setters);
|
|
785
875
|
if (!varId)
|
|
786
876
|
return undefined;
|
|
877
|
+
const basePath = local?.expr.kind === "read" ? local.expr.path ?? [] : [];
|
|
787
878
|
return {
|
|
788
879
|
expr: {
|
|
789
880
|
kind: "cond",
|
|
790
881
|
args: [
|
|
791
|
-
{ kind: "eq", args: [{ kind: "read", var: varId }, { kind: "lit", value: null }] },
|
|
792
|
-
{ kind: "lit", value:
|
|
793
|
-
{ kind: "read", var: varId, path: read.path }
|
|
882
|
+
{ kind: "eq", args: [{ kind: "read", var: varId, ...(basePath.length > 0 ? { path: basePath } : {}) }, { kind: "lit", value: null }] },
|
|
883
|
+
{ kind: "lit", value: fallback },
|
|
884
|
+
{ kind: "read", var: varId, path: [...basePath, ...read.path] }
|
|
794
885
|
]
|
|
795
886
|
},
|
|
796
887
|
reads: [varId]
|
|
@@ -799,7 +890,7 @@ function nullishOptionalReadExpr(expression, setters) {
|
|
|
799
890
|
function optionalReadPath(expression) {
|
|
800
891
|
if (ts.isIdentifier(expression))
|
|
801
892
|
return { base: expression.text, path: [], optional: false };
|
|
802
|
-
if (
|
|
893
|
+
if (isPropertyAccessLike(expression)) {
|
|
803
894
|
const base = optionalReadPath(expression.expression);
|
|
804
895
|
if (!base)
|
|
805
896
|
return undefined;
|
|
@@ -875,9 +966,13 @@ function modeledReadExpr(expression, setters, locals) {
|
|
|
875
966
|
reads: local.reads
|
|
876
967
|
};
|
|
877
968
|
}
|
|
878
|
-
const
|
|
969
|
+
const setter = setterForName(base, setters);
|
|
970
|
+
const stateVar = setter?.varId;
|
|
879
971
|
if (!stateVar)
|
|
880
972
|
return undefined;
|
|
973
|
+
if (setter.domain.kind === "tagged" && segments.length > 0 && segments[0] !== setter.domain.tag) {
|
|
974
|
+
return { expr: { kind: "lit", value: firstValue(taggedPathDomain(setter.domain, segments) ?? { kind: "tokens", count: 1 }) }, reads: [] };
|
|
975
|
+
}
|
|
881
976
|
return {
|
|
882
977
|
expr: { kind: "read", var: stateVar, ...(segments.length > 0 ? { path: segments } : {}) },
|
|
883
978
|
reads: [stateVar]
|
|
@@ -936,10 +1031,36 @@ function effectWriteVars(effect) {
|
|
|
936
1031
|
function stateNameForVar(varId, setters) {
|
|
937
1032
|
return [...setters.values()].find((setter) => setter.varId === varId)?.stateName;
|
|
938
1033
|
}
|
|
1034
|
+
function componentGuardLocalsFor(attribute, setters) {
|
|
1035
|
+
const body = enclosingFunctionBody(attribute);
|
|
1036
|
+
if (!body)
|
|
1037
|
+
return new Map();
|
|
1038
|
+
const locals = new Map();
|
|
1039
|
+
for (const statement of body.statements) {
|
|
1040
|
+
if (statement.pos > attribute.pos)
|
|
1041
|
+
break;
|
|
1042
|
+
if (ts.isReturnStatement(statement))
|
|
1043
|
+
break;
|
|
1044
|
+
bindConstStatement(statement, setters, locals, true);
|
|
1045
|
+
}
|
|
1046
|
+
return locals;
|
|
1047
|
+
}
|
|
1048
|
+
function enclosingFunctionBody(node) {
|
|
1049
|
+
let current = node;
|
|
1050
|
+
while (current) {
|
|
1051
|
+
if ((ts.isFunctionDeclaration(current) || ts.isFunctionExpression(current) || ts.isArrowFunction(current)) && current.body && ts.isBlock(current.body)) {
|
|
1052
|
+
return current.body;
|
|
1053
|
+
}
|
|
1054
|
+
current = current.parent;
|
|
1055
|
+
}
|
|
1056
|
+
return undefined;
|
|
1057
|
+
}
|
|
939
1058
|
function callSummaryFromHandler(handler, setters, initialLocals = new Map()) {
|
|
940
1059
|
const body = handler.body;
|
|
941
1060
|
if (ts.isCallExpression(body))
|
|
942
1061
|
return { call: body, locals: new Map(initialLocals) };
|
|
1062
|
+
if (ts.isVoidExpression(body) && ts.isCallExpression(body.expression))
|
|
1063
|
+
return { call: body.expression, locals: new Map(initialLocals) };
|
|
943
1064
|
if (ts.isBlock(body)) {
|
|
944
1065
|
const locals = new Map(initialLocals);
|
|
945
1066
|
for (let index = 0; index < body.statements.length; index += 1) {
|
|
@@ -947,13 +1068,30 @@ function callSummaryFromHandler(handler, setters, initialLocals = new Map()) {
|
|
|
947
1068
|
const isLast = index === body.statements.length - 1;
|
|
948
1069
|
if (isLast && ts.isExpressionStatement(statement) && ts.isCallExpression(statement.expression))
|
|
949
1070
|
return { call: statement.expression, locals };
|
|
1071
|
+
if (isLast && ts.isExpressionStatement(statement) && ts.isVoidExpression(statement.expression) && ts.isCallExpression(statement.expression.expression))
|
|
1072
|
+
return { call: statement.expression.expression, locals };
|
|
950
1073
|
if (!bindConstStatement(statement, setters, locals))
|
|
951
1074
|
return undefined;
|
|
952
1075
|
}
|
|
953
1076
|
}
|
|
954
1077
|
return undefined;
|
|
955
1078
|
}
|
|
956
|
-
function
|
|
1079
|
+
function swrMutateTransition(source, fileName, node, attr, component, call, locator) {
|
|
1080
|
+
if (!ts.isIdentifier(call.expression) || call.expression.text !== "mutate")
|
|
1081
|
+
return undefined;
|
|
1082
|
+
return {
|
|
1083
|
+
id: `${component}.${attr}.mutate`,
|
|
1084
|
+
cls: "user",
|
|
1085
|
+
label: labelForEvent(attr, locator),
|
|
1086
|
+
source: [{ file: fileName, ...lineAndColumn(source, node) }],
|
|
1087
|
+
guard: { kind: "lit", value: true },
|
|
1088
|
+
effect: { kind: "seq", effects: [] },
|
|
1089
|
+
reads: [],
|
|
1090
|
+
writes: [],
|
|
1091
|
+
confidence: "exact"
|
|
1092
|
+
};
|
|
1093
|
+
}
|
|
1094
|
+
function bindConstStatement(statement, setters, locals, partialBoolean = false) {
|
|
957
1095
|
if (!ts.isVariableStatement(statement))
|
|
958
1096
|
return false;
|
|
959
1097
|
if ((ts.getCombinedNodeFlags(statement.declarationList) & ts.NodeFlags.Const) === 0)
|
|
@@ -961,7 +1099,8 @@ function bindConstStatement(statement, setters, locals) {
|
|
|
961
1099
|
for (const declaration of statement.declarationList.declarations) {
|
|
962
1100
|
if (!ts.isIdentifier(declaration.name) || !declaration.initializer)
|
|
963
1101
|
return false;
|
|
964
|
-
const binding = valueExpr(declaration.initializer, setters, locals)
|
|
1102
|
+
const binding = valueExpr(declaration.initializer, setters, locals) ??
|
|
1103
|
+
(partialBoolean ? parseConjunctiveGuardExpression(declaration.initializer, setters, locals) : booleanExpr(declaration.initializer, setters, locals));
|
|
965
1104
|
if (!binding)
|
|
966
1105
|
return false;
|
|
967
1106
|
locals.set(declaration.name.text, binding);
|
|
@@ -1847,7 +1986,7 @@ function combineParsedGuards(guards) {
|
|
|
1847
1986
|
reads: [...new Set(parsed.flatMap((guard) => guard.reads))]
|
|
1848
1987
|
};
|
|
1849
1988
|
}
|
|
1850
|
-
function renderGuardFor(eventAttribute, setters, warnings, source, component) {
|
|
1989
|
+
function renderGuardFor(eventAttribute, setters, warnings, source, component, locals = new Map()) {
|
|
1851
1990
|
const element = jsxElementForAttribute(eventAttribute);
|
|
1852
1991
|
if (!element)
|
|
1853
1992
|
return undefined;
|
|
@@ -1857,7 +1996,7 @@ function renderGuardFor(eventAttribute, setters, warnings, source, component) {
|
|
|
1857
1996
|
if (ts.isBinaryExpression(parent) &&
|
|
1858
1997
|
parent.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken &&
|
|
1859
1998
|
parent.right === current) {
|
|
1860
|
-
const parsed =
|
|
1999
|
+
const parsed = parseConjunctiveGuardExpression(parent.left, setters, locals);
|
|
1861
2000
|
if (!parsed) {
|
|
1862
2001
|
warnings.push({ message: `Unsupported render guard ${component}.${eventAttribute.name.getText(source)}`, ...lineAndColumn(source, parent.left) });
|
|
1863
2002
|
return undefined;
|
|
@@ -1865,7 +2004,7 @@ function renderGuardFor(eventAttribute, setters, warnings, source, component) {
|
|
|
1865
2004
|
return parsed;
|
|
1866
2005
|
}
|
|
1867
2006
|
if (ts.isConditionalExpression(parent) && parent.whenTrue === current) {
|
|
1868
|
-
const parsed =
|
|
2007
|
+
const parsed = parseConjunctiveGuardExpression(parent.condition, setters, locals);
|
|
1869
2008
|
if (!parsed) {
|
|
1870
2009
|
warnings.push({ message: `Unsupported render guard ${component}.${eventAttribute.name.getText(source)}`, ...lineAndColumn(source, parent.condition) });
|
|
1871
2010
|
return undefined;
|
|
@@ -1873,7 +2012,7 @@ function renderGuardFor(eventAttribute, setters, warnings, source, component) {
|
|
|
1873
2012
|
return parsed;
|
|
1874
2013
|
}
|
|
1875
2014
|
if (ts.isConditionalExpression(parent) && parent.whenFalse === current) {
|
|
1876
|
-
const parsed =
|
|
2015
|
+
const parsed = parseConjunctiveGuardExpression(parent.condition, setters, locals);
|
|
1877
2016
|
if (!parsed) {
|
|
1878
2017
|
warnings.push({ message: `Unsupported render guard ${component}.${eventAttribute.name.getText(source)}`, ...lineAndColumn(source, parent.condition) });
|
|
1879
2018
|
return undefined;
|
|
@@ -1897,50 +2036,73 @@ function jsxElementForAttribute(attribute) {
|
|
|
1897
2036
|
return element.parent;
|
|
1898
2037
|
return ts.isJsxSelfClosingElement(element) ? element : undefined;
|
|
1899
2038
|
}
|
|
1900
|
-
function disabledGuardFor(eventAttribute, setters, warnings, source, component) {
|
|
2039
|
+
function disabledGuardFor(eventAttribute, setters, warnings, source, component, locals = new Map()) {
|
|
1901
2040
|
const attrs = eventAttribute.parent;
|
|
1902
2041
|
if (!ts.isJsxAttributes(attrs))
|
|
1903
2042
|
return undefined;
|
|
1904
|
-
const disabled = attrs.properties.find((property) => ts.isJsxAttribute(property) && ts.isIdentifier(property.name) && (property.name.text === "disabled" || property.name.text === "aria-disabled"));
|
|
2043
|
+
const disabled = attrs.properties.find((property) => ts.isJsxAttribute(property) && ts.isIdentifier(property.name) && (property.name.text === "disabled" || property.name.text === "aria-disabled")) ?? submitButtonDisabledAttribute(eventAttribute);
|
|
1905
2044
|
if (!disabled)
|
|
1906
2045
|
return undefined;
|
|
1907
|
-
const parsed = jsxAttributeBoolean(disabled, setters);
|
|
2046
|
+
const parsed = jsxAttributeBoolean(disabled, setters, locals);
|
|
1908
2047
|
if (!parsed) {
|
|
1909
2048
|
warnings.push({ message: `Unsupported disabled guard ${component}.${eventAttribute.name.getText(source)}`, ...lineAndColumn(source, disabled) });
|
|
1910
2049
|
return undefined;
|
|
1911
2050
|
}
|
|
1912
2051
|
return { expr: { kind: "not", args: [parsed.expr] }, reads: parsed.reads };
|
|
1913
2052
|
}
|
|
1914
|
-
function
|
|
2053
|
+
function submitButtonDisabledAttribute(eventAttribute) {
|
|
2054
|
+
if (!ts.isIdentifier(eventAttribute.name) || eventAttribute.name.text !== "onSubmit")
|
|
2055
|
+
return undefined;
|
|
2056
|
+
const element = jsxElementForAttribute(eventAttribute);
|
|
2057
|
+
if (!element || !ts.isJsxElement(element))
|
|
2058
|
+
return undefined;
|
|
2059
|
+
let found;
|
|
2060
|
+
const visit = (node) => {
|
|
2061
|
+
if (found)
|
|
2062
|
+
return;
|
|
2063
|
+
if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
|
|
2064
|
+
const tag = ts.isIdentifier(node.tagName) ? node.tagName.text : undefined;
|
|
2065
|
+
if (tag === "button" && stringAttribute(node.attributes, "type") === "submit") {
|
|
2066
|
+
found = node.attributes.properties.find((property) => ts.isJsxAttribute(property) && ts.isIdentifier(property.name) && (property.name.text === "disabled" || property.name.text === "aria-disabled"));
|
|
2067
|
+
if (found)
|
|
2068
|
+
return;
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
ts.forEachChild(node, visit);
|
|
2072
|
+
};
|
|
2073
|
+
visit(element);
|
|
2074
|
+
return found;
|
|
2075
|
+
}
|
|
2076
|
+
function jsxAttributeBoolean(attribute, setters, locals = new Map()) {
|
|
1915
2077
|
if (!attribute.initializer)
|
|
1916
2078
|
return { expr: { kind: "lit", value: true }, reads: [] };
|
|
1917
2079
|
if (ts.isStringLiteral(attribute.initializer))
|
|
1918
2080
|
return { expr: { kind: "lit", value: attribute.initializer.text === "true" }, reads: [] };
|
|
1919
2081
|
if (!ts.isJsxExpression(attribute.initializer) || !attribute.initializer.expression)
|
|
1920
2082
|
return undefined;
|
|
1921
|
-
return
|
|
2083
|
+
return parseConjunctiveGuardExpression(attribute.initializer.expression, setters, locals);
|
|
1922
2084
|
}
|
|
1923
|
-
function parseGuardExpression(expression, setters) {
|
|
2085
|
+
function parseGuardExpression(expression, setters, locals = new Map()) {
|
|
1924
2086
|
if (expression.kind === ts.SyntaxKind.TrueKeyword)
|
|
1925
2087
|
return { expr: { kind: "lit", value: true }, reads: [] };
|
|
1926
2088
|
if (expression.kind === ts.SyntaxKind.FalseKeyword)
|
|
1927
2089
|
return { expr: { kind: "lit", value: false }, reads: [] };
|
|
1928
|
-
if (ts.isIdentifier(expression) ||
|
|
1929
|
-
return valueExpr(expression, setters,
|
|
2090
|
+
if (ts.isIdentifier(expression) || isPropertyAccessLike(expression))
|
|
2091
|
+
return valueExpr(expression, setters, locals);
|
|
1930
2092
|
if (ts.isPrefixUnaryExpression(expression) && expression.operator === ts.SyntaxKind.ExclamationToken) {
|
|
1931
|
-
const parsed = parseGuardExpression(expression.operand, setters);
|
|
2093
|
+
const parsed = parseGuardExpression(expression.operand, setters, locals);
|
|
1932
2094
|
return parsed ? { expr: { kind: "not", args: [parsed.expr] }, reads: parsed.reads } : undefined;
|
|
1933
2095
|
}
|
|
1934
2096
|
if (ts.isParenthesizedExpression(expression))
|
|
1935
|
-
return parseGuardExpression(expression.expression, setters);
|
|
2097
|
+
return parseGuardExpression(expression.expression, setters, locals);
|
|
1936
2098
|
if (ts.isBinaryExpression(expression))
|
|
1937
|
-
return parseBinaryGuardExpression(expression, setters);
|
|
2099
|
+
return parseBinaryGuardExpression(expression, setters, locals);
|
|
1938
2100
|
return undefined;
|
|
1939
2101
|
}
|
|
1940
|
-
function parseBinaryGuardExpression(expression, setters) {
|
|
2102
|
+
function parseBinaryGuardExpression(expression, setters, locals = new Map()) {
|
|
1941
2103
|
if (expression.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken || expression.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
|
|
1942
|
-
const left = parseGuardExpression(expression.left, setters);
|
|
1943
|
-
const right = parseGuardExpression(expression.right, setters);
|
|
2104
|
+
const left = parseGuardExpression(expression.left, setters, locals);
|
|
2105
|
+
const right = parseGuardExpression(expression.right, setters, locals);
|
|
1944
2106
|
if (!left || !right)
|
|
1945
2107
|
return undefined;
|
|
1946
2108
|
return {
|
|
@@ -1952,8 +2114,8 @@ function parseBinaryGuardExpression(expression, setters) {
|
|
|
1952
2114
|
expression.operatorToken.kind === ts.SyntaxKind.EqualsEqualsToken ||
|
|
1953
2115
|
expression.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken ||
|
|
1954
2116
|
expression.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsToken) {
|
|
1955
|
-
const left = parseGuardOperand(expression.left, setters);
|
|
1956
|
-
const right = parseGuardOperand(expression.right, setters);
|
|
2117
|
+
const left = parseGuardOperand(expression.left, setters, locals);
|
|
2118
|
+
const right = parseGuardOperand(expression.right, setters, locals);
|
|
1957
2119
|
if (!left || !right)
|
|
1958
2120
|
return undefined;
|
|
1959
2121
|
return {
|
|
@@ -1966,16 +2128,52 @@ function parseBinaryGuardExpression(expression, setters) {
|
|
|
1966
2128
|
}
|
|
1967
2129
|
return undefined;
|
|
1968
2130
|
}
|
|
1969
|
-
function parseGuardOperand(expression, setters) {
|
|
2131
|
+
function parseGuardOperand(expression, setters, locals = new Map()) {
|
|
1970
2132
|
const value = literalValue(expression);
|
|
1971
2133
|
if (value !== undefined)
|
|
1972
2134
|
return { expr: { kind: "lit", value }, reads: [] };
|
|
1973
|
-
if (ts.isIdentifier(expression) ||
|
|
1974
|
-
return valueExpr(expression, setters,
|
|
1975
|
-
return parseGuardExpression(expression, setters);
|
|
2135
|
+
if (ts.isIdentifier(expression) || isPropertyAccessLike(expression))
|
|
2136
|
+
return valueExpr(expression, setters, locals);
|
|
2137
|
+
return parseGuardExpression(expression, setters, locals);
|
|
2138
|
+
}
|
|
2139
|
+
function parseConjunctiveGuardExpression(expression, setters, locals = new Map()) {
|
|
2140
|
+
if (ts.isParenthesizedExpression(expression))
|
|
2141
|
+
return parseConjunctiveGuardExpression(expression.expression, setters, locals);
|
|
2142
|
+
if (ts.isBinaryExpression(expression) && expression.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
|
|
2143
|
+
return combineParsedGuards([
|
|
2144
|
+
parseConjunctiveGuardExpression(expression.left, setters, locals),
|
|
2145
|
+
parseConjunctiveGuardExpression(expression.right, setters, locals)
|
|
2146
|
+
]);
|
|
2147
|
+
}
|
|
2148
|
+
return parseGuardExpression(expression, setters, locals);
|
|
1976
2149
|
}
|
|
1977
2150
|
function stateVarForName(name, setters) {
|
|
1978
|
-
return
|
|
2151
|
+
return setterForName(name, setters)?.varId;
|
|
2152
|
+
}
|
|
2153
|
+
function setterForName(name, setters) {
|
|
2154
|
+
return setters.get(name) ?? [...setters.values()].find((setter) => setter.stateName === name);
|
|
2155
|
+
}
|
|
2156
|
+
function taggedPathDomain(domain, path) {
|
|
2157
|
+
const [field, ...rest] = path;
|
|
2158
|
+
if (!field)
|
|
2159
|
+
return domain;
|
|
2160
|
+
const variants = Object.values(domain.variants).filter((variant) => variant.kind === "record");
|
|
2161
|
+
const fieldDomains = variants.map((variant) => variant.fields[field]).filter((candidate) => Boolean(candidate));
|
|
2162
|
+
if (fieldDomains.length === 0)
|
|
2163
|
+
return undefined;
|
|
2164
|
+
const first = fieldDomains[0];
|
|
2165
|
+
if (rest.length === 0)
|
|
2166
|
+
return first;
|
|
2167
|
+
return first.kind === "record" ? domainAtRecordPath(first, rest) : undefined;
|
|
2168
|
+
}
|
|
2169
|
+
function domainAtRecordPath(domain, path) {
|
|
2170
|
+
const [field, ...rest] = path;
|
|
2171
|
+
if (!field)
|
|
2172
|
+
return domain;
|
|
2173
|
+
const next = domain.fields[field];
|
|
2174
|
+
if (!next || rest.length === 0)
|
|
2175
|
+
return next;
|
|
2176
|
+
return next.kind === "record" ? domainAtRecordPath(next, rest) : undefined;
|
|
1979
2177
|
}
|
|
1980
2178
|
function andGuard(left, right) {
|
|
1981
2179
|
if (isTrueLiteral(left))
|
|
@@ -2074,12 +2272,15 @@ function isInputValueExpression(node, parameter) {
|
|
|
2074
2272
|
function propertyAccessPath(node) {
|
|
2075
2273
|
if (ts.isIdentifier(node))
|
|
2076
2274
|
return [node.text];
|
|
2077
|
-
if (
|
|
2275
|
+
if (isPropertyAccessLike(node)) {
|
|
2078
2276
|
const base = propertyAccessPath(node.expression);
|
|
2079
2277
|
return base ? [...base, node.name.text] : undefined;
|
|
2080
2278
|
}
|
|
2081
2279
|
return undefined;
|
|
2082
2280
|
}
|
|
2281
|
+
function isPropertyAccessLike(node) {
|
|
2282
|
+
return ts.isPropertyAccessExpression(node) || ts.isPropertyAccessChain(node);
|
|
2283
|
+
}
|
|
2083
2284
|
function valueClassForDomain(domain) {
|
|
2084
2285
|
if (domain.kind === "enum")
|
|
2085
2286
|
return domain.values.join("|") || "enum";
|