modality-ts 0.0.0 → 0.0.2

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.
@@ -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,CAuBrF;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,CAyI3H"}
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,CA2I3H"}
@@ -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 = [];
@@ -96,7 +97,7 @@ export function extractUseStateSkeleton(sourceText, options = {}) {
96
97
  const stateName = node.name.elements[0];
97
98
  const setterName = node.name.elements[1];
98
99
  if (ts.isBindingElement(stateName) && ts.isIdentifier(stateName.name)) {
99
- const domain = inferUseStateDomain(node.initializer);
100
+ const domain = inferUseStateDomain(node.initializer, typeAliases);
100
101
  const component = nextComponent ?? "Anonymous";
101
102
  const varId = `local:${component}.${stateName.name.text}`;
102
103
  if (!options.stateVars) {
@@ -159,9 +160,10 @@ export function extractUseStateSkeleton(sourceText, options = {}) {
159
160
  ts.forEachChild(node, (child) => visit(child, nextComponent));
160
161
  return;
161
162
  }
163
+ const guardLocals = componentGuardLocalsFor(node, setters);
162
164
  const guard = combineParsedGuards([
163
- renderGuardFor(node, setters, warnings, source, nextComponent ?? "Anonymous"),
164
- disabledGuardFor(node, setters, warnings, source, nextComponent ?? "Anonymous")
165
+ renderGuardFor(node, setters, warnings, source, nextComponent ?? "Anonymous", guardLocals),
166
+ disabledGuardFor(node, setters, warnings, source, nextComponent ?? "Anonymous", guardLocals)
165
167
  ]);
166
168
  const extracted = transitionsFromJsxAttribute(source, fileName, node, setters, handlers, nextComponent ?? "Anonymous", effectApis, options.asyncOutcomes ?? {}, guard, warnings);
167
169
  transitions.push(...extracted);
@@ -223,10 +225,10 @@ function shortHash(value) {
223
225
  }
224
226
  return (hash >>> 0).toString(36).padStart(6, "0").slice(0, 6);
225
227
  }
226
- function inferUseStateDomain(call) {
228
+ function inferUseStateDomain(call, typeAliases = new Map()) {
227
229
  const typeArg = call.typeArguments?.[0];
228
230
  if (typeArg)
229
- return inferDomainFromTypeNode(typeArg);
231
+ return inferDomainFromTypeNode(typeArg, typeAliases);
230
232
  const initial = call.arguments[0];
231
233
  if (!initial)
232
234
  return { kind: "tokens", count: 1 };
@@ -289,10 +291,10 @@ function domainFromLiteralType(node) {
289
291
  return { kind: "option", inner: { kind: "tokens", count: 1 } };
290
292
  return { kind: "tokens", count: 1 };
291
293
  }
292
- function domainFromUnion(node) {
294
+ function domainFromUnion(node, typeAliases = new Map()) {
293
295
  const nonNull = node.types.filter((part) => part.kind !== ts.SyntaxKind.UndefinedKeyword && !(ts.isLiteralTypeNode(part) && part.literal.kind === ts.SyntaxKind.NullKeyword));
294
296
  if (nonNull.length !== node.types.length && nonNull.length > 0) {
295
- return { kind: "option", inner: nonNull.length === 1 ? inferDomainFromTypeNode(nonNull[0]) : domainFromUnionMembers(nonNull) };
297
+ return { kind: "option", inner: nonNull.length === 1 ? inferDomainFromTypeNode(nonNull[0], typeAliases) : domainFromUnionMembers(nonNull) };
296
298
  }
297
299
  return domainFromUnionMembers(node.types);
298
300
  }
@@ -353,14 +355,27 @@ function domainFromTypeLiteral(node, omitField) {
353
355
  }
354
356
  return { kind: "record", fields };
355
357
  }
356
- function domainFromTypeReference(node) {
358
+ function domainFromTypeReference(node, typeAliases = new Map()) {
357
359
  const name = node.typeName.getText();
360
+ const alias = typeAliases.get(name);
361
+ if (alias)
362
+ return inferDomainFromTypeNode(alias, typeAliases);
358
363
  if ((name === "Array" || name === "ReadonlyArray") && node.typeArguments?.length === 1)
359
364
  return { kind: "lengthCat" };
360
365
  if (name === "Record")
361
366
  return { kind: "tokens", count: 1 };
362
367
  return { kind: "tokens", count: 1 };
363
368
  }
369
+ function typeAliasDeclarations(source) {
370
+ const aliases = new Map();
371
+ const visit = (node) => {
372
+ if (ts.isTypeAliasDeclaration(node) && ts.isIdentifier(node.name))
373
+ aliases.set(node.name.text, node.type);
374
+ ts.forEachChild(node, visit);
375
+ };
376
+ visit(source);
377
+ return aliases;
378
+ }
364
379
  function isUseStateCall(node) {
365
380
  return ts.isCallExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === "useState";
366
381
  }
@@ -504,9 +519,10 @@ function transitionsFromComponentPropAttribute(source, fileName, node, setters,
504
519
  const handler = handlerExpression(expression, handlers);
505
520
  if (!handler)
506
521
  return [];
522
+ const guardLocals = componentGuardLocalsFor(node, setters);
507
523
  const callerGuard = combineParsedGuards([
508
- renderGuardFor(node, setters, warnings, source, component),
509
- disabledGuardFor(node, setters, warnings, source, component)
524
+ renderGuardFor(node, setters, warnings, source, component, guardLocals),
525
+ disabledGuardFor(node, setters, warnings, source, component, guardLocals)
510
526
  ]);
511
527
  return tagStableIdKey(transitionsFromResolvedHandler(source, fileName, node, trigger.attr, handler, setters, handlers, component, effectApis, asyncOutcomes, combineParsedGuards([trigger.guard, callerGuard]), trigger.locator, warnings), handler);
512
528
  }
@@ -936,6 +952,30 @@ function effectWriteVars(effect) {
936
952
  function stateNameForVar(varId, setters) {
937
953
  return [...setters.values()].find((setter) => setter.varId === varId)?.stateName;
938
954
  }
955
+ function componentGuardLocalsFor(attribute, setters) {
956
+ const body = enclosingFunctionBody(attribute);
957
+ if (!body)
958
+ return new Map();
959
+ const locals = new Map();
960
+ for (const statement of body.statements) {
961
+ if (statement.pos > attribute.pos)
962
+ break;
963
+ if (ts.isReturnStatement(statement))
964
+ break;
965
+ bindConstStatement(statement, setters, locals, true);
966
+ }
967
+ return locals;
968
+ }
969
+ function enclosingFunctionBody(node) {
970
+ let current = node;
971
+ while (current) {
972
+ if ((ts.isFunctionDeclaration(current) || ts.isFunctionExpression(current) || ts.isArrowFunction(current)) && current.body && ts.isBlock(current.body)) {
973
+ return current.body;
974
+ }
975
+ current = current.parent;
976
+ }
977
+ return undefined;
978
+ }
939
979
  function callSummaryFromHandler(handler, setters, initialLocals = new Map()) {
940
980
  const body = handler.body;
941
981
  if (ts.isCallExpression(body))
@@ -953,7 +993,7 @@ function callSummaryFromHandler(handler, setters, initialLocals = new Map()) {
953
993
  }
954
994
  return undefined;
955
995
  }
956
- function bindConstStatement(statement, setters, locals) {
996
+ function bindConstStatement(statement, setters, locals, partialBoolean = false) {
957
997
  if (!ts.isVariableStatement(statement))
958
998
  return false;
959
999
  if ((ts.getCombinedNodeFlags(statement.declarationList) & ts.NodeFlags.Const) === 0)
@@ -961,7 +1001,8 @@ function bindConstStatement(statement, setters, locals) {
961
1001
  for (const declaration of statement.declarationList.declarations) {
962
1002
  if (!ts.isIdentifier(declaration.name) || !declaration.initializer)
963
1003
  return false;
964
- const binding = valueExpr(declaration.initializer, setters, locals);
1004
+ const binding = valueExpr(declaration.initializer, setters, locals) ??
1005
+ (partialBoolean ? parseConjunctiveGuardExpression(declaration.initializer, setters, locals) : booleanExpr(declaration.initializer, setters, locals));
965
1006
  if (!binding)
966
1007
  return false;
967
1008
  locals.set(declaration.name.text, binding);
@@ -1847,7 +1888,7 @@ function combineParsedGuards(guards) {
1847
1888
  reads: [...new Set(parsed.flatMap((guard) => guard.reads))]
1848
1889
  };
1849
1890
  }
1850
- function renderGuardFor(eventAttribute, setters, warnings, source, component) {
1891
+ function renderGuardFor(eventAttribute, setters, warnings, source, component, locals = new Map()) {
1851
1892
  const element = jsxElementForAttribute(eventAttribute);
1852
1893
  if (!element)
1853
1894
  return undefined;
@@ -1857,7 +1898,7 @@ function renderGuardFor(eventAttribute, setters, warnings, source, component) {
1857
1898
  if (ts.isBinaryExpression(parent) &&
1858
1899
  parent.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken &&
1859
1900
  parent.right === current) {
1860
- const parsed = parseGuardExpression(parent.left, setters);
1901
+ const parsed = parseConjunctiveGuardExpression(parent.left, setters, locals);
1861
1902
  if (!parsed) {
1862
1903
  warnings.push({ message: `Unsupported render guard ${component}.${eventAttribute.name.getText(source)}`, ...lineAndColumn(source, parent.left) });
1863
1904
  return undefined;
@@ -1865,7 +1906,7 @@ function renderGuardFor(eventAttribute, setters, warnings, source, component) {
1865
1906
  return parsed;
1866
1907
  }
1867
1908
  if (ts.isConditionalExpression(parent) && parent.whenTrue === current) {
1868
- const parsed = parseGuardExpression(parent.condition, setters);
1909
+ const parsed = parseConjunctiveGuardExpression(parent.condition, setters, locals);
1869
1910
  if (!parsed) {
1870
1911
  warnings.push({ message: `Unsupported render guard ${component}.${eventAttribute.name.getText(source)}`, ...lineAndColumn(source, parent.condition) });
1871
1912
  return undefined;
@@ -1873,7 +1914,7 @@ function renderGuardFor(eventAttribute, setters, warnings, source, component) {
1873
1914
  return parsed;
1874
1915
  }
1875
1916
  if (ts.isConditionalExpression(parent) && parent.whenFalse === current) {
1876
- const parsed = parseGuardExpression(parent.condition, setters);
1917
+ const parsed = parseConjunctiveGuardExpression(parent.condition, setters, locals);
1877
1918
  if (!parsed) {
1878
1919
  warnings.push({ message: `Unsupported render guard ${component}.${eventAttribute.name.getText(source)}`, ...lineAndColumn(source, parent.condition) });
1879
1920
  return undefined;
@@ -1897,50 +1938,73 @@ function jsxElementForAttribute(attribute) {
1897
1938
  return element.parent;
1898
1939
  return ts.isJsxSelfClosingElement(element) ? element : undefined;
1899
1940
  }
1900
- function disabledGuardFor(eventAttribute, setters, warnings, source, component) {
1941
+ function disabledGuardFor(eventAttribute, setters, warnings, source, component, locals = new Map()) {
1901
1942
  const attrs = eventAttribute.parent;
1902
1943
  if (!ts.isJsxAttributes(attrs))
1903
1944
  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"));
1945
+ 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
1946
  if (!disabled)
1906
1947
  return undefined;
1907
- const parsed = jsxAttributeBoolean(disabled, setters);
1948
+ const parsed = jsxAttributeBoolean(disabled, setters, locals);
1908
1949
  if (!parsed) {
1909
1950
  warnings.push({ message: `Unsupported disabled guard ${component}.${eventAttribute.name.getText(source)}`, ...lineAndColumn(source, disabled) });
1910
1951
  return undefined;
1911
1952
  }
1912
1953
  return { expr: { kind: "not", args: [parsed.expr] }, reads: parsed.reads };
1913
1954
  }
1914
- function jsxAttributeBoolean(attribute, setters) {
1955
+ function submitButtonDisabledAttribute(eventAttribute) {
1956
+ if (!ts.isIdentifier(eventAttribute.name) || eventAttribute.name.text !== "onSubmit")
1957
+ return undefined;
1958
+ const element = jsxElementForAttribute(eventAttribute);
1959
+ if (!element || !ts.isJsxElement(element))
1960
+ return undefined;
1961
+ let found;
1962
+ const visit = (node) => {
1963
+ if (found)
1964
+ return;
1965
+ if (ts.isJsxOpeningElement(node) || ts.isJsxSelfClosingElement(node)) {
1966
+ const tag = ts.isIdentifier(node.tagName) ? node.tagName.text : undefined;
1967
+ if (tag === "button" && stringAttribute(node.attributes, "type") === "submit") {
1968
+ found = node.attributes.properties.find((property) => ts.isJsxAttribute(property) && ts.isIdentifier(property.name) && (property.name.text === "disabled" || property.name.text === "aria-disabled"));
1969
+ if (found)
1970
+ return;
1971
+ }
1972
+ }
1973
+ ts.forEachChild(node, visit);
1974
+ };
1975
+ visit(element);
1976
+ return found;
1977
+ }
1978
+ function jsxAttributeBoolean(attribute, setters, locals = new Map()) {
1915
1979
  if (!attribute.initializer)
1916
1980
  return { expr: { kind: "lit", value: true }, reads: [] };
1917
1981
  if (ts.isStringLiteral(attribute.initializer))
1918
1982
  return { expr: { kind: "lit", value: attribute.initializer.text === "true" }, reads: [] };
1919
1983
  if (!ts.isJsxExpression(attribute.initializer) || !attribute.initializer.expression)
1920
1984
  return undefined;
1921
- return parseGuardExpression(attribute.initializer.expression, setters);
1985
+ return parseConjunctiveGuardExpression(attribute.initializer.expression, setters, locals);
1922
1986
  }
1923
- function parseGuardExpression(expression, setters) {
1987
+ function parseGuardExpression(expression, setters, locals = new Map()) {
1924
1988
  if (expression.kind === ts.SyntaxKind.TrueKeyword)
1925
1989
  return { expr: { kind: "lit", value: true }, reads: [] };
1926
1990
  if (expression.kind === ts.SyntaxKind.FalseKeyword)
1927
1991
  return { expr: { kind: "lit", value: false }, reads: [] };
1928
1992
  if (ts.isIdentifier(expression) || ts.isPropertyAccessExpression(expression))
1929
- return valueExpr(expression, setters, new Map());
1993
+ return valueExpr(expression, setters, locals);
1930
1994
  if (ts.isPrefixUnaryExpression(expression) && expression.operator === ts.SyntaxKind.ExclamationToken) {
1931
- const parsed = parseGuardExpression(expression.operand, setters);
1995
+ const parsed = parseGuardExpression(expression.operand, setters, locals);
1932
1996
  return parsed ? { expr: { kind: "not", args: [parsed.expr] }, reads: parsed.reads } : undefined;
1933
1997
  }
1934
1998
  if (ts.isParenthesizedExpression(expression))
1935
- return parseGuardExpression(expression.expression, setters);
1999
+ return parseGuardExpression(expression.expression, setters, locals);
1936
2000
  if (ts.isBinaryExpression(expression))
1937
- return parseBinaryGuardExpression(expression, setters);
2001
+ return parseBinaryGuardExpression(expression, setters, locals);
1938
2002
  return undefined;
1939
2003
  }
1940
- function parseBinaryGuardExpression(expression, setters) {
2004
+ function parseBinaryGuardExpression(expression, setters, locals = new Map()) {
1941
2005
  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);
2006
+ const left = parseGuardExpression(expression.left, setters, locals);
2007
+ const right = parseGuardExpression(expression.right, setters, locals);
1944
2008
  if (!left || !right)
1945
2009
  return undefined;
1946
2010
  return {
@@ -1952,8 +2016,8 @@ function parseBinaryGuardExpression(expression, setters) {
1952
2016
  expression.operatorToken.kind === ts.SyntaxKind.EqualsEqualsToken ||
1953
2017
  expression.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsEqualsToken ||
1954
2018
  expression.operatorToken.kind === ts.SyntaxKind.ExclamationEqualsToken) {
1955
- const left = parseGuardOperand(expression.left, setters);
1956
- const right = parseGuardOperand(expression.right, setters);
2019
+ const left = parseGuardOperand(expression.left, setters, locals);
2020
+ const right = parseGuardOperand(expression.right, setters, locals);
1957
2021
  if (!left || !right)
1958
2022
  return undefined;
1959
2023
  return {
@@ -1966,13 +2030,24 @@ function parseBinaryGuardExpression(expression, setters) {
1966
2030
  }
1967
2031
  return undefined;
1968
2032
  }
1969
- function parseGuardOperand(expression, setters) {
2033
+ function parseGuardOperand(expression, setters, locals = new Map()) {
1970
2034
  const value = literalValue(expression);
1971
2035
  if (value !== undefined)
1972
2036
  return { expr: { kind: "lit", value }, reads: [] };
1973
2037
  if (ts.isIdentifier(expression) || ts.isPropertyAccessExpression(expression))
1974
- return valueExpr(expression, setters, new Map());
1975
- return parseGuardExpression(expression, setters);
2038
+ return valueExpr(expression, setters, locals);
2039
+ return parseGuardExpression(expression, setters, locals);
2040
+ }
2041
+ function parseConjunctiveGuardExpression(expression, setters, locals = new Map()) {
2042
+ if (ts.isParenthesizedExpression(expression))
2043
+ return parseConjunctiveGuardExpression(expression.expression, setters, locals);
2044
+ if (ts.isBinaryExpression(expression) && expression.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
2045
+ return combineParsedGuards([
2046
+ parseConjunctiveGuardExpression(expression.left, setters, locals),
2047
+ parseConjunctiveGuardExpression(expression.right, setters, locals)
2048
+ ]);
2049
+ }
2050
+ return parseGuardExpression(expression, setters, locals);
1976
2051
  }
1977
2052
  function stateVarForName(name, setters) {
1978
2053
  return [...setters.values()].find((setter) => setter.stateName === name)?.varId;