@elaraai/tsserver-plugin-east 1.0.80 → 1.0.81
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/index.cjs +672 -51
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -345,10 +345,58 @@ function resolvesWithinOwnPackage(sourceFileName, specifierText) {
|
|
|
345
345
|
// ../east-diagnostics/dist/src/rules/prefer-some-none.js
|
|
346
346
|
var NAME3 = "prefer-some-none";
|
|
347
347
|
var CODE3 = 990003;
|
|
348
|
+
function membersOf(types) {
|
|
349
|
+
return types.flatMap((type) => type.isUnion() ? type.types : [type]);
|
|
350
|
+
}
|
|
351
|
+
function literalsOf(type) {
|
|
352
|
+
return membersOf([type]).flatMap((m) => m.isStringLiteral() ? [m.value] : []);
|
|
353
|
+
}
|
|
354
|
+
function variantMembers(expected, at, ctx) {
|
|
355
|
+
const out = [];
|
|
356
|
+
for (const member of membersOf(expected)) {
|
|
357
|
+
if (isEastExprType(member))
|
|
358
|
+
continue;
|
|
359
|
+
if (ctx.checker.getPropertyOfType(member, "value") === void 0)
|
|
360
|
+
continue;
|
|
361
|
+
const tag = ctx.checker.getPropertyOfType(member, "type");
|
|
362
|
+
if (tag === void 0)
|
|
363
|
+
continue;
|
|
364
|
+
out.push({ member, tags: literalsOf(ctx.checker.getTypeOfSymbolAtLocation(tag, at)) });
|
|
365
|
+
}
|
|
366
|
+
return out;
|
|
367
|
+
}
|
|
368
|
+
function propertyTypes(types, name, at, ctx) {
|
|
369
|
+
return membersOf(types).flatMap((m) => {
|
|
370
|
+
const prop = ctx.checker.getPropertyOfType(m, name);
|
|
371
|
+
return prop === void 0 ? [] : [ctx.checker.getTypeOfSymbolAtLocation(prop, at)];
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
function uninformative(type, ctx) {
|
|
375
|
+
const t = ctx.ts;
|
|
376
|
+
return (type.flags & (t.TypeFlags.Any | t.TypeFlags.Unknown | t.TypeFlags.TypeParameter)) !== 0;
|
|
377
|
+
}
|
|
378
|
+
function expectedTypes(e, ctx, depth = 0) {
|
|
379
|
+
const t = ctx.ts;
|
|
380
|
+
const parent = e.parent;
|
|
381
|
+
if (depth < 8 && t.isPropertyAssignment(parent) && parent.initializer === e && (t.isIdentifier(parent.name) || t.isStringLiteral(parent.name))) {
|
|
382
|
+
const literal = expectedTypes(parent.parent, ctx, depth + 1);
|
|
383
|
+
if (literal.length > 0)
|
|
384
|
+
return propertyTypes(literal, parent.name.text, e, ctx);
|
|
385
|
+
}
|
|
386
|
+
if (depth < 8 && t.isCallExpression(parent) && parent.arguments[1] === e && t.isIdentifier(parent.expression) && parent.expression.text === "variant" && resolvesToEastImport(parent.expression, ctx.checker, t)) {
|
|
387
|
+
const tag = parent.arguments[0];
|
|
388
|
+
if (tag === void 0 || !t.isStringLiteralLike(tag))
|
|
389
|
+
return [];
|
|
390
|
+
const matches = variantMembers(expectedTypes(parent, ctx, depth + 1), e, ctx).filter((m) => m.tags.includes(tag.text));
|
|
391
|
+
return propertyTypes(matches.map((m) => m.member), "value", e, ctx);
|
|
392
|
+
}
|
|
393
|
+
const direct = ctx.checker.getContextualType(e);
|
|
394
|
+
return direct !== void 0 && !uninformative(direct, ctx) ? [direct] : [];
|
|
395
|
+
}
|
|
348
396
|
var preferSomeNone = {
|
|
349
397
|
name: NAME3,
|
|
350
398
|
code: CODE3,
|
|
351
|
-
description: 'Prefer some()/none over variant("some", \u2026)/variant("none", null).',
|
|
399
|
+
description: 'Prefer some()/none over variant("some", \u2026)/variant("none", null) for an Option.',
|
|
352
400
|
check(node, ctx) {
|
|
353
401
|
const t = ctx.ts;
|
|
354
402
|
if (!t.isCallExpression(node))
|
|
@@ -364,6 +412,8 @@ var preferSomeNone = {
|
|
|
364
412
|
const tag = first.text;
|
|
365
413
|
if (tag !== "some" && tag !== "none")
|
|
366
414
|
return;
|
|
415
|
+
if (variantMembers(expectedTypes(node, ctx), node, ctx).some((m) => m.tags.some((other) => other !== "some" && other !== "none")))
|
|
416
|
+
return;
|
|
367
417
|
const sf = ctx.sourceFile;
|
|
368
418
|
const start = node.getStart(sf);
|
|
369
419
|
ctx.report({
|
|
@@ -371,7 +421,7 @@ var preferSomeNone = {
|
|
|
371
421
|
code: CODE3,
|
|
372
422
|
start,
|
|
373
423
|
length: node.getEnd() - start,
|
|
374
|
-
messageText: tag === "some" ? 'Use `some(value)` instead of `variant("some", value)`.' : 'Use `none` instead of `variant("none", null)`.',
|
|
424
|
+
messageText: (tag === "some" ? 'Use `some(value)` instead of `variant("some", value)`.' : 'Use `none` instead of `variant("none", null)`.') + " If this is the case of a variant that is not an Option, type the value it builds (`ValueTypeOf<typeof T>`) so the case can be told apart.",
|
|
375
425
|
category: "warning"
|
|
376
426
|
});
|
|
377
427
|
}
|
|
@@ -395,16 +445,48 @@ function expectsVariant(type) {
|
|
|
395
445
|
}
|
|
396
446
|
return false;
|
|
397
447
|
}
|
|
448
|
+
function propertyNamed(node, name, t) {
|
|
449
|
+
for (const p of node.properties) {
|
|
450
|
+
if ((t.isPropertyAssignment(p) || t.isShorthandPropertyAssignment(p)) && (t.isIdentifier(p.name) || t.isStringLiteral(p.name)) && p.name.text === name) {
|
|
451
|
+
return p;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return void 0;
|
|
455
|
+
}
|
|
456
|
+
function spelledAsVariant(node, ctx) {
|
|
457
|
+
const t = ctx.ts;
|
|
458
|
+
if (node.properties.length !== 2)
|
|
459
|
+
return false;
|
|
460
|
+
const tag = propertyNamed(node, "type", t);
|
|
461
|
+
if (tag === void 0 || propertyNamed(node, "value", t) === void 0)
|
|
462
|
+
return false;
|
|
463
|
+
const tagType = ctx.checker.getTypeAtLocation(t.isPropertyAssignment(tag) ? tag.initializer : tag.name);
|
|
464
|
+
const members = tagType.isUnion() ? tagType.types : [tagType];
|
|
465
|
+
return members.every((m) => (m.flags & t.TypeFlags.StringLike) !== 0);
|
|
466
|
+
}
|
|
467
|
+
function publishedShape(type, t) {
|
|
468
|
+
const members = (type.isUnion() ? type.types : [type]).filter((m) => (m.flags & (t.TypeFlags.Null | t.TypeFlags.Undefined)) === 0);
|
|
469
|
+
return members.length > 0 && members.every((m) => {
|
|
470
|
+
if ((m.flags & t.TypeFlags.Object) === 0)
|
|
471
|
+
return false;
|
|
472
|
+
const declarations = (m.aliasSymbol ?? m.symbol)?.declarations ?? [];
|
|
473
|
+
return declarations.length > 0 && declarations.every((d) => d.getSourceFile().isDeclarationFile);
|
|
474
|
+
});
|
|
475
|
+
}
|
|
398
476
|
var noHandrolledVariant = {
|
|
399
477
|
name: NAME4,
|
|
400
478
|
code: CODE4,
|
|
401
|
-
description: "Disallow plain object
|
|
479
|
+
description: "Disallow a variant built as a plain `{ type, value }` object literal \u2014 where a variant is expected, or anywhere in East source; use variant()/some()/none.",
|
|
402
480
|
check(node, ctx) {
|
|
403
481
|
const t = ctx.ts;
|
|
404
482
|
if (!t.isObjectLiteralExpression(node))
|
|
405
483
|
return;
|
|
484
|
+
if (propertyNamed(node, "type", t) === void 0)
|
|
485
|
+
return;
|
|
406
486
|
const contextualType = ctx.checker.getContextualType(node);
|
|
407
|
-
|
|
487
|
+
const inVariantSlot = contextualType !== void 0 && expectsVariant(contextualType);
|
|
488
|
+
const byHand = !inVariantSlot && importsEastPackage(ctx.sourceFile, t) && spelledAsVariant(node, ctx) && (contextualType === void 0 || !publishedShape(contextualType, t));
|
|
489
|
+
if (!inVariantSlot && !byHand)
|
|
408
490
|
return;
|
|
409
491
|
const sf = ctx.sourceFile;
|
|
410
492
|
const start = node.getStart(sf);
|
|
@@ -413,7 +495,7 @@ var noHandrolledVariant = {
|
|
|
413
495
|
code: CODE4,
|
|
414
496
|
start,
|
|
415
497
|
length: node.getEnd() - start,
|
|
416
|
-
messageText: 'Hand-rolled variant: build with `variant("Tag", value)`, `some(value)`, or `none` from @elaraai/east \u2014 never a plain `{ type, value }` object literal.',
|
|
498
|
+
messageText: 'Hand-rolled variant: build with `variant("Tag", value)`, `some(value)`, or `none` from @elaraai/east \u2014 never a plain `{ type, value }` object literal, which lacks the symbol East\'s variants carry.',
|
|
417
499
|
category: "warning"
|
|
418
500
|
});
|
|
419
501
|
}
|
|
@@ -1463,20 +1545,24 @@ function isJsx2(node, t) {
|
|
|
1463
1545
|
function isHostTemplate(e, t) {
|
|
1464
1546
|
return t.isTemplateExpression(unparen(e, t));
|
|
1465
1547
|
}
|
|
1466
|
-
function
|
|
1548
|
+
function hasIrArgument(call, ctx) {
|
|
1549
|
+
return call.arguments.some((a) => isEastExprType(ctx.checker.getTypeAtLocation(a)) || containsEastBuilder(a, ctx));
|
|
1550
|
+
}
|
|
1551
|
+
function isEastValueConstructor(expr, ctx) {
|
|
1552
|
+
const t = ctx.ts;
|
|
1467
1553
|
if (t.isCallExpression(expr)) {
|
|
1468
1554
|
const callee = expr.expression;
|
|
1469
1555
|
if (t.isIdentifier(callee) && VALUE_CONSTRUCTORS.has(callee.text))
|
|
1470
|
-
return
|
|
1556
|
+
return hasIrArgument(expr, ctx);
|
|
1471
1557
|
return t.isPropertyAccessExpression(callee) && t.isIdentifier(callee.expression) && callee.expression.text === "East" && callee.name.text === "value";
|
|
1472
1558
|
}
|
|
1473
|
-
return
|
|
1559
|
+
return false;
|
|
1474
1560
|
}
|
|
1475
1561
|
function isEastBuilderCall(call, ctx) {
|
|
1476
1562
|
const t = ctx.ts;
|
|
1477
1563
|
const callee = call.expression;
|
|
1478
1564
|
if (t.isIdentifier(callee) && VALUE_CONSTRUCTORS.has(callee.text))
|
|
1479
|
-
return
|
|
1565
|
+
return hasIrArgument(call, ctx);
|
|
1480
1566
|
const root = chainRootReceiver(callee, ctx);
|
|
1481
1567
|
if (t.isIdentifier(root) && root.text === "East")
|
|
1482
1568
|
return true;
|
|
@@ -1503,7 +1589,7 @@ function returnBuildsEast(r, ctx) {
|
|
|
1503
1589
|
return false;
|
|
1504
1590
|
if (isEastDefinitionType(ctx.checker.getTypeAtLocation(r)))
|
|
1505
1591
|
return false;
|
|
1506
|
-
if (isEastValueConstructor(r,
|
|
1592
|
+
if (isEastValueConstructor(r, ctx))
|
|
1507
1593
|
return true;
|
|
1508
1594
|
if (isEastExprType(ctx.checker.getTypeAtLocation(r)))
|
|
1509
1595
|
return true;
|
|
@@ -1513,6 +1599,62 @@ function returnBuildsEast(r, ctx) {
|
|
|
1513
1599
|
return true;
|
|
1514
1600
|
return containsEastBuilder(r, ctx);
|
|
1515
1601
|
}
|
|
1602
|
+
function buildsEastProgram(root, ctx) {
|
|
1603
|
+
const t = ctx.ts;
|
|
1604
|
+
if (!t.isIdentifier(root))
|
|
1605
|
+
return false;
|
|
1606
|
+
if (isBlockBuilderType(ctx.checker.getTypeAtLocation(root)))
|
|
1607
|
+
return true;
|
|
1608
|
+
if (!resolvesToEastImport(root, ctx.checker, t))
|
|
1609
|
+
return false;
|
|
1610
|
+
if (root.text === "East")
|
|
1611
|
+
return true;
|
|
1612
|
+
const imp = importDeclarationOf(ctx.checker.getSymbolAtLocation(root), t);
|
|
1613
|
+
return imp !== void 0 && t.isStringLiteral(imp.moduleSpecifier) && imp.moduleSpecifier.text === "@elaraai/e3";
|
|
1614
|
+
}
|
|
1615
|
+
function flowsIntoEastProgram(call, ctx) {
|
|
1616
|
+
const t = ctx.ts;
|
|
1617
|
+
let node = call;
|
|
1618
|
+
for (; ; ) {
|
|
1619
|
+
const parent = node.parent;
|
|
1620
|
+
if (parent === void 0)
|
|
1621
|
+
return false;
|
|
1622
|
+
if (t.isArrayLiteralExpression(parent) || t.isObjectLiteralExpression(parent) || t.isPropertyAssignment(parent) || t.isParenthesizedExpression(parent) || t.isSpreadElement(parent) || t.isTemplateSpan(parent) || t.isTemplateExpression(parent) || t.isNewExpression(parent)) {
|
|
1623
|
+
node = parent;
|
|
1624
|
+
continue;
|
|
1625
|
+
}
|
|
1626
|
+
if (t.isCallExpression(parent) && parent.expression !== node) {
|
|
1627
|
+
return buildsEastProgram(chainRootReceiver(parent.expression, ctx), ctx);
|
|
1628
|
+
}
|
|
1629
|
+
return false;
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
function feedsEastProgram(name, ctx) {
|
|
1633
|
+
const t = ctx.ts;
|
|
1634
|
+
const symbol = ctx.checker.getSymbolAtLocation(name);
|
|
1635
|
+
if (symbol === void 0)
|
|
1636
|
+
return false;
|
|
1637
|
+
let found = false;
|
|
1638
|
+
const visit = (n) => {
|
|
1639
|
+
if (found)
|
|
1640
|
+
return;
|
|
1641
|
+
if (t.isCallExpression(n) && t.isIdentifier(n.expression) && ctx.checker.getSymbolAtLocation(n.expression) === symbol) {
|
|
1642
|
+
if (insideBlockScope(n, ctx) || flowsIntoEastProgram(n, ctx)) {
|
|
1643
|
+
found = true;
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
t.forEachChild(n, visit);
|
|
1648
|
+
};
|
|
1649
|
+
visit(ctx.sourceFile);
|
|
1650
|
+
return found;
|
|
1651
|
+
}
|
|
1652
|
+
function isValueConstruction(e, t) {
|
|
1653
|
+
const r = unparen(e, t);
|
|
1654
|
+
if (t.isIdentifier(r))
|
|
1655
|
+
return r.text === "none";
|
|
1656
|
+
return t.isCallExpression(r) && t.isIdentifier(r.expression) && VALUE_CONSTRUCTORS.has(r.expression.text);
|
|
1657
|
+
}
|
|
1516
1658
|
function returnExpressions2(fn, t) {
|
|
1517
1659
|
if (fn.body === void 0)
|
|
1518
1660
|
return [];
|
|
@@ -1539,12 +1681,16 @@ var noModuleScopeEastMacro = {
|
|
|
1539
1681
|
return;
|
|
1540
1682
|
let fn;
|
|
1541
1683
|
let reportNode;
|
|
1684
|
+
let nameNode;
|
|
1542
1685
|
if (t.isFunctionDeclaration(node) && node.body !== void 0) {
|
|
1543
1686
|
fn = node;
|
|
1544
1687
|
reportNode = node.name ?? node;
|
|
1688
|
+
nameNode = node.name;
|
|
1545
1689
|
} else if (t.isVariableDeclaration(node) && node.initializer !== void 0 && (t.isArrowFunction(node.initializer) || t.isFunctionExpression(node.initializer))) {
|
|
1546
1690
|
fn = node.initializer;
|
|
1547
1691
|
reportNode = node.name;
|
|
1692
|
+
if (t.isIdentifier(node.name))
|
|
1693
|
+
nameNode = node.name;
|
|
1548
1694
|
}
|
|
1549
1695
|
if (fn === void 0 || reportNode === void 0)
|
|
1550
1696
|
return;
|
|
@@ -1558,9 +1704,10 @@ var noModuleScopeEastMacro = {
|
|
|
1558
1704
|
return;
|
|
1559
1705
|
if (rs.length === 0)
|
|
1560
1706
|
return;
|
|
1561
|
-
const
|
|
1562
|
-
const
|
|
1563
|
-
|
|
1707
|
+
const everyBuildsIr = rs.every((r) => returnBuildsEast(r, ctx));
|
|
1708
|
+
const everyBuildsValue = !everyBuildsIr && rs.every((r) => returnBuildsEast(r, ctx) || isValueConstruction(r, t));
|
|
1709
|
+
const everyHostKey = !everyBuildsIr && !everyBuildsValue && rs.every((r) => isHostTemplate(r, t));
|
|
1710
|
+
if (!everyBuildsIr && !((everyBuildsValue || everyHostKey) && nameNode !== void 0 && feedsEastProgram(nameNode, ctx)))
|
|
1564
1711
|
return;
|
|
1565
1712
|
const sf = ctx.sourceFile;
|
|
1566
1713
|
const start = reportNode.getStart(sf);
|
|
@@ -1918,6 +2065,158 @@ var noBuildTimeClock = {
|
|
|
1918
2065
|
}
|
|
1919
2066
|
};
|
|
1920
2067
|
|
|
2068
|
+
// ../east-diagnostics/dist/src/east-value.js
|
|
2069
|
+
function declaredByEast(decl) {
|
|
2070
|
+
const file = decl.getSourceFile().fileName.replace(/\\/g, "/");
|
|
2071
|
+
return /(?:@elaraai\/east|\/libs\/east)\/(?:dist\/)?src\//.test(file);
|
|
2072
|
+
}
|
|
2073
|
+
function declaredByEastPackage(decl) {
|
|
2074
|
+
const sf = decl.getSourceFile();
|
|
2075
|
+
return sf.isDeclarationFile && /(?:\/@elaraai\/|\/libs\/)/.test(sf.fileName.replace(/\\/g, "/"));
|
|
2076
|
+
}
|
|
2077
|
+
function constituents(type, t) {
|
|
2078
|
+
const members = type.isUnion() ? type.types : [type];
|
|
2079
|
+
return members.filter((m) => (m.flags & (t.TypeFlags.Null | t.TypeFlags.Undefined | t.TypeFlags.Void)) === 0);
|
|
2080
|
+
}
|
|
2081
|
+
function isStructConstituent(type, t) {
|
|
2082
|
+
return (type.symbol?.declarations ?? []).some((d) => t.isMappedTypeNode(d) && declaredByEast(d));
|
|
2083
|
+
}
|
|
2084
|
+
function isVariantConstituent(type, checker) {
|
|
2085
|
+
if ((type.flags & 1) !== 0)
|
|
2086
|
+
return false;
|
|
2087
|
+
for (const prop of checker.getPropertiesOfType(type)) {
|
|
2088
|
+
if (!String(prop.escapedName).startsWith("__@variant_symbol"))
|
|
2089
|
+
continue;
|
|
2090
|
+
if ((prop.declarations ?? []).some(declaredByEast))
|
|
2091
|
+
return true;
|
|
2092
|
+
}
|
|
2093
|
+
return false;
|
|
2094
|
+
}
|
|
2095
|
+
function isSortedConstituent(type) {
|
|
2096
|
+
const name = type.symbol?.name;
|
|
2097
|
+
return (name === "SortedSet" || name === "SortedMap") && (type.symbol?.declarations ?? []).some(declaredByEast);
|
|
2098
|
+
}
|
|
2099
|
+
function isEastValueShape(type, ctx) {
|
|
2100
|
+
if (isEastExprType(type))
|
|
2101
|
+
return false;
|
|
2102
|
+
return constituents(type, ctx.ts).some((m) => isStructConstituent(m, ctx.ts) || isVariantConstituent(m, ctx.checker) || isSortedConstituent(m));
|
|
2103
|
+
}
|
|
2104
|
+
function isRecordShape(type, ctx) {
|
|
2105
|
+
return constituents(type, ctx.ts).some((m) => isStructConstituent(m, ctx.ts) || isVariantConstituent(m, ctx.checker));
|
|
2106
|
+
}
|
|
2107
|
+
function jsPrintDiffers(type, t) {
|
|
2108
|
+
const same = t.TypeFlags.StringLike | t.TypeFlags.BigIntLike | t.TypeFlags.BooleanLike;
|
|
2109
|
+
return constituents(type, t).some((m) => (m.flags & same) === 0);
|
|
2110
|
+
}
|
|
2111
|
+
function isGlobalBuiltin(id, ctx) {
|
|
2112
|
+
const declarations = ctx.checker.getSymbolAtLocation(id)?.declarations ?? [];
|
|
2113
|
+
return declarations.length > 0 && declarations.every((d) => {
|
|
2114
|
+
const file = d.getSourceFile();
|
|
2115
|
+
return ctx.program?.isSourceFileDefaultLibrary(file) ?? /\/typescript\/lib\/lib\.[^/]*\.d\.ts$/.test(file.fileName.replace(/\\/g, "/"));
|
|
2116
|
+
});
|
|
2117
|
+
}
|
|
2118
|
+
function skipValueWrappers(e, t) {
|
|
2119
|
+
let cur = e;
|
|
2120
|
+
for (; ; ) {
|
|
2121
|
+
if (t.isParenthesizedExpression(cur) || t.isNonNullExpression(cur) || t.isAsExpression(cur) || t.isTypeAssertionExpression(cur) || t.isSatisfiesExpression(cur)) {
|
|
2122
|
+
cur = cur.expression;
|
|
2123
|
+
} else {
|
|
2124
|
+
return cur;
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
}
|
|
2128
|
+
var ELEMENT_READS = /* @__PURE__ */ new Set(["get", "at"]);
|
|
2129
|
+
function isEastValueExpression(e, ctx) {
|
|
2130
|
+
return eastValueExpression(e, ctx, 0);
|
|
2131
|
+
}
|
|
2132
|
+
function eastValueExpression(e, ctx, depth) {
|
|
2133
|
+
const t = ctx.ts;
|
|
2134
|
+
const expr = skipValueWrappers(e, t);
|
|
2135
|
+
const type = ctx.checker.getTypeAtLocation(expr);
|
|
2136
|
+
if (isEastExprType(type))
|
|
2137
|
+
return false;
|
|
2138
|
+
if (isEastValueShape(type, ctx))
|
|
2139
|
+
return true;
|
|
2140
|
+
if (t.isPropertyAccessExpression(expr)) {
|
|
2141
|
+
return isRecordShape(ctx.checker.getTypeAtLocation(expr.expression), ctx);
|
|
2142
|
+
}
|
|
2143
|
+
if (t.isElementAccessExpression(expr)) {
|
|
2144
|
+
return eastValueExpression(expr.expression, ctx, depth);
|
|
2145
|
+
}
|
|
2146
|
+
if (t.isCallExpression(expr) && t.isPropertyAccessExpression(expr.expression) && ELEMENT_READS.has(expr.expression.name.text)) {
|
|
2147
|
+
return eastValueExpression(expr.expression.expression, ctx, depth);
|
|
2148
|
+
}
|
|
2149
|
+
if (t.isIdentifier(expr) && depth < 3) {
|
|
2150
|
+
const decl = ctx.checker.getSymbolAtLocation(expr)?.valueDeclaration;
|
|
2151
|
+
if (decl !== void 0 && t.isVariableDeclaration(decl) && decl.initializer !== void 0 && t.isVariableDeclarationList(decl.parent) && (decl.parent.flags & t.NodeFlags.Const) !== 0) {
|
|
2152
|
+
return eastValueExpression(decl.initializer, ctx, depth + 1);
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
return false;
|
|
2156
|
+
}
|
|
2157
|
+
function isValueConstructorCall(call, ctx) {
|
|
2158
|
+
const t = ctx.ts;
|
|
2159
|
+
const callee = call.expression;
|
|
2160
|
+
return t.isIdentifier(callee) && (callee.text === "variant" || callee.text === "some") && resolvesToEastImport(callee, ctx.checker, t);
|
|
2161
|
+
}
|
|
2162
|
+
function parameterTypeAt(signature, index, call, ctx) {
|
|
2163
|
+
const t = ctx.ts;
|
|
2164
|
+
const params = signature.getParameters();
|
|
2165
|
+
if (params.length === 0)
|
|
2166
|
+
return void 0;
|
|
2167
|
+
const param = params[Math.min(index, params.length - 1)];
|
|
2168
|
+
const type = ctx.checker.getTypeOfSymbolAtLocation(param, call);
|
|
2169
|
+
const declaration = param.valueDeclaration;
|
|
2170
|
+
if (declaration !== void 0 && t.isParameter(declaration) && declaration.dotDotDotToken !== void 0) {
|
|
2171
|
+
return ctx.checker.getTypeArguments(type)[0];
|
|
2172
|
+
}
|
|
2173
|
+
return type;
|
|
2174
|
+
}
|
|
2175
|
+
function flowOf(e, ctx) {
|
|
2176
|
+
const t = ctx.ts;
|
|
2177
|
+
if (insideBlockScope(e, ctx))
|
|
2178
|
+
return "program";
|
|
2179
|
+
let node = e;
|
|
2180
|
+
let east = false;
|
|
2181
|
+
for (; ; ) {
|
|
2182
|
+
const parent = node.parent;
|
|
2183
|
+
if (parent === void 0)
|
|
2184
|
+
return east ? "value" : "host";
|
|
2185
|
+
if (t.isParenthesizedExpression(parent) || t.isAsExpression(parent) || t.isSatisfiesExpression(parent) || t.isNonNullExpression(parent) || t.isTypeAssertionExpression(parent) || t.isArrayLiteralExpression(parent) || t.isSpreadElement(parent)) {
|
|
2186
|
+
node = parent;
|
|
2187
|
+
continue;
|
|
2188
|
+
}
|
|
2189
|
+
if (t.isPropertyAssignment(parent) && parent.initializer === node || t.isShorthandPropertyAssignment(parent)) {
|
|
2190
|
+
const literal = parent.parent;
|
|
2191
|
+
const contextual = ctx.checker.getContextualType(literal);
|
|
2192
|
+
if (contextual !== void 0 && isRecordShape(contextual, ctx))
|
|
2193
|
+
east = true;
|
|
2194
|
+
node = literal;
|
|
2195
|
+
continue;
|
|
2196
|
+
}
|
|
2197
|
+
const args = t.isCallExpression(parent) || t.isNewExpression(parent) ? parent.arguments ?? [] : [];
|
|
2198
|
+
if ((t.isCallExpression(parent) || t.isNewExpression(parent)) && args.includes(node)) {
|
|
2199
|
+
if (t.isCallExpression(parent) && isValueConstructorCall(parent, ctx)) {
|
|
2200
|
+
east = true;
|
|
2201
|
+
node = parent;
|
|
2202
|
+
continue;
|
|
2203
|
+
}
|
|
2204
|
+
const signature = ctx.checker.getResolvedSignature(parent);
|
|
2205
|
+
const declaration = signature?.getDeclaration();
|
|
2206
|
+
if (signature !== void 0 && declaration !== void 0 && declaredByEastPackage(declaration)) {
|
|
2207
|
+
const index = args.indexOf(node);
|
|
2208
|
+
const param = parameterTypeAt(signature, index, parent, ctx);
|
|
2209
|
+
return param !== void 0 && isEastExprType(param) ? "program" : "value";
|
|
2210
|
+
}
|
|
2211
|
+
return east ? "value" : "host";
|
|
2212
|
+
}
|
|
2213
|
+
return east ? "value" : "host";
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
function landsInEastSlot(e, ctx) {
|
|
2217
|
+
return flowOf(e, ctx) === "value";
|
|
2218
|
+
}
|
|
2219
|
+
|
|
1921
2220
|
// ../east-diagnostics/dist/src/rules/no-handrolled-value-type-mirror.js
|
|
1922
2221
|
var NAME23 = "no-handrolled-value-type-mirror";
|
|
1923
2222
|
var CODE23 = 990028;
|
|
@@ -1945,70 +2244,165 @@ function eastTypeValueInFile(name, ctx) {
|
|
|
1945
2244
|
}
|
|
1946
2245
|
return false;
|
|
1947
2246
|
}
|
|
2247
|
+
function membersOf2(node) {
|
|
2248
|
+
return node.members;
|
|
2249
|
+
}
|
|
2250
|
+
function mentionsEastImport(typeNode, ctx) {
|
|
2251
|
+
const t = ctx.ts;
|
|
2252
|
+
let found = false;
|
|
2253
|
+
const visit = (n) => {
|
|
2254
|
+
if (found)
|
|
2255
|
+
return;
|
|
2256
|
+
if (t.isTypeReferenceNode(n)) {
|
|
2257
|
+
const name = t.isIdentifier(n.typeName) ? n.typeName : t.isQualifiedName(n.typeName) ? leftmost(n.typeName, t) : void 0;
|
|
2258
|
+
if (name !== void 0 && resolvesToEastImport(name, ctx.checker, t)) {
|
|
2259
|
+
found = true;
|
|
2260
|
+
return;
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
t.forEachChild(n, visit);
|
|
2264
|
+
};
|
|
2265
|
+
visit(typeNode);
|
|
2266
|
+
return found;
|
|
2267
|
+
}
|
|
2268
|
+
function leftmost(q, t) {
|
|
2269
|
+
let cur = q;
|
|
2270
|
+
while (t.isQualifiedName(cur))
|
|
2271
|
+
cur = cur.left;
|
|
2272
|
+
return cur;
|
|
2273
|
+
}
|
|
2274
|
+
function holdsEastValue(shape, ctx) {
|
|
2275
|
+
const t = ctx.ts;
|
|
2276
|
+
return membersOf2(shape).some((m) => t.isPropertySignature(m) && m.type !== void 0 && isEastValueShape(ctx.checker.getTypeFromTypeNode(m.type), ctx));
|
|
2277
|
+
}
|
|
2278
|
+
function isFactoryInput(node, ctx) {
|
|
2279
|
+
const t = ctx.ts;
|
|
2280
|
+
const sf = ctx.sourceFile;
|
|
2281
|
+
return membersOf2(node).some((m) => {
|
|
2282
|
+
const type = t.isPropertySignature(m) ? m.type : void 0;
|
|
2283
|
+
return type !== void 0 && /\b(?:SubtypeExprOrValue|ExprType)</.test(type.getText(sf));
|
|
2284
|
+
});
|
|
2285
|
+
}
|
|
2286
|
+
function isVariantShapedLiteral(node, t) {
|
|
2287
|
+
if (!t.isTypeLiteralNode(node))
|
|
2288
|
+
return false;
|
|
2289
|
+
const names = new Set(node.members.flatMap((m) => m.name !== void 0 && t.isIdentifier(m.name) ? [m.name.text] : []));
|
|
2290
|
+
return names.has("type") && names.has("value");
|
|
2291
|
+
}
|
|
2292
|
+
function report2(ctx, node, messageText) {
|
|
2293
|
+
const start = node.getStart(ctx.sourceFile);
|
|
2294
|
+
ctx.report({ ruleName: NAME23, code: CODE23, start, length: node.getEnd() - start, messageText, category: "suggestion" });
|
|
2295
|
+
}
|
|
1948
2296
|
var noHandrolledValueTypeMirror = {
|
|
1949
2297
|
name: NAME23,
|
|
1950
2298
|
code: CODE23,
|
|
1951
|
-
description: "A hand-authored
|
|
2299
|
+
description: "A hand-authored type mirroring an East type (Foo beside FooType, a *Like stand-in, an `as { type; value }` assertion) \u2014 derive it with ValueTypeOf<typeof XType> instead.",
|
|
1952
2300
|
check(node, ctx) {
|
|
1953
2301
|
const t = ctx.ts;
|
|
2302
|
+
if (t.isAsExpression(node) || t.isTypeAssertionExpression(node)) {
|
|
2303
|
+
if (!isVariantShapedLiteral(node.type, t))
|
|
2304
|
+
return;
|
|
2305
|
+
if (!importsEastPackage(ctx.sourceFile, t))
|
|
2306
|
+
return;
|
|
2307
|
+
report2(ctx, node.type, "This `{ type, value }` assertion hand-mirrors an East variant \u2014 it hides the value's real type. Narrow the typed value on its tag (`v.type === \u2026`), or type it with `ValueTypeOf<typeof XType>`.");
|
|
2308
|
+
return;
|
|
2309
|
+
}
|
|
1954
2310
|
let name;
|
|
2311
|
+
let shape;
|
|
1955
2312
|
if (t.isInterfaceDeclaration(node)) {
|
|
1956
2313
|
name = node.name;
|
|
2314
|
+
shape = node;
|
|
1957
2315
|
} else if (t.isTypeAliasDeclaration(node) && t.isTypeLiteralNode(node.type)) {
|
|
1958
2316
|
name = node.name;
|
|
2317
|
+
shape = node.type;
|
|
1959
2318
|
}
|
|
1960
|
-
if (name === void 0)
|
|
2319
|
+
if (name === void 0 || shape === void 0)
|
|
1961
2320
|
return;
|
|
1962
2321
|
if (!importsEastPackage(ctx.sourceFile, t))
|
|
1963
2322
|
return;
|
|
2323
|
+
if (isFactoryInput(shape, ctx))
|
|
2324
|
+
return;
|
|
2325
|
+
if (name.text.length > "Like".length && name.text.endsWith("Like") && (mentionsEastImport(shape, ctx) || holdsEastValue(shape, ctx))) {
|
|
2326
|
+
report2(ctx, name, `\`${name.text}\` is a loose stand-in for an East value's type \u2014 it drifts silently when the East type changes. Type the value with \`ValueTypeOf<typeof XType>\` of the East type it holds.`);
|
|
2327
|
+
return;
|
|
2328
|
+
}
|
|
1964
2329
|
const base = name.text.endsWith("Value") ? name.text.slice(0, -"Value".length) : name.text;
|
|
1965
2330
|
const counterpart = `${base}Type`;
|
|
1966
2331
|
if (counterpart === name.text)
|
|
1967
2332
|
return;
|
|
1968
2333
|
if (!eastTypeValueInFile(counterpart, ctx))
|
|
1969
2334
|
return;
|
|
1970
|
-
|
|
1971
|
-
const start = name.getStart(sf);
|
|
1972
|
-
ctx.report({
|
|
1973
|
-
ruleName: NAME23,
|
|
1974
|
-
code: CODE23,
|
|
1975
|
-
start,
|
|
1976
|
-
length: name.getEnd() - start,
|
|
1977
|
-
messageText: `\`${name.text}\` hand-mirrors the East type \`${counterpart}\` \u2014 it drifts silently when the East type gains a field. Derive it: \`type ${name.text} = ValueTypeOf<typeof ${counterpart}>\`.`,
|
|
1978
|
-
category: "suggestion"
|
|
1979
|
-
});
|
|
2335
|
+
report2(ctx, name, `\`${name.text}\` hand-mirrors the East type \`${counterpart}\` \u2014 it drifts silently when the East type gains a field. Derive it: \`type ${name.text} = ValueTypeOf<typeof ${counterpart}>\`.`);
|
|
1980
2336
|
}
|
|
1981
2337
|
};
|
|
1982
2338
|
|
|
1983
2339
|
// ../east-diagnostics/dist/src/rules/no-host-comparison-on-east-values.js
|
|
1984
2340
|
var NAME24 = "no-host-comparison-on-east-values";
|
|
1985
2341
|
var CODE24 = 990029;
|
|
1986
|
-
var VALUE_SHAPE_NAMES = /* @__PURE__ */ new Set(["variant", "option", "SortedMap", "SortedSet"]);
|
|
1987
|
-
function isEastValueShapeType(type) {
|
|
1988
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1989
|
-
const stack = [type];
|
|
1990
|
-
while (stack.length > 0) {
|
|
1991
|
-
const current = stack.pop();
|
|
1992
|
-
if (current === void 0 || seen.has(current))
|
|
1993
|
-
continue;
|
|
1994
|
-
seen.add(current);
|
|
1995
|
-
const name = current.aliasSymbol?.name ?? current.symbol?.name;
|
|
1996
|
-
if (name !== void 0 && VALUE_SHAPE_NAMES.has(name))
|
|
1997
|
-
return true;
|
|
1998
|
-
if (current.isUnionOrIntersection())
|
|
1999
|
-
stack.push(...current.types);
|
|
2000
|
-
}
|
|
2001
|
-
return false;
|
|
2002
|
-
}
|
|
2003
2342
|
function isNullish(e, t) {
|
|
2004
2343
|
return e.kind === t.SyntaxKind.NullKeyword || t.isIdentifier(e) && e.text === "undefined";
|
|
2005
2344
|
}
|
|
2345
|
+
function isEastObject(e, ctx) {
|
|
2346
|
+
const type = ctx.checker.getTypeAtLocation(e);
|
|
2347
|
+
if (isEastValueShape(type, ctx))
|
|
2348
|
+
return true;
|
|
2349
|
+
const objectLike = (type.flags & ctx.ts.TypeFlags.Object) !== 0 || type.isUnion() && type.types.some((m) => (m.flags & ctx.ts.TypeFlags.Object) !== 0);
|
|
2350
|
+
return objectLike && isEastValueExpression(e, ctx);
|
|
2351
|
+
}
|
|
2352
|
+
function returnedExpressions(fn, t) {
|
|
2353
|
+
if (!t.isBlock(fn.body))
|
|
2354
|
+
return [fn.body];
|
|
2355
|
+
const out = [];
|
|
2356
|
+
const visit = (n) => {
|
|
2357
|
+
if (t.isFunctionLike(n))
|
|
2358
|
+
return;
|
|
2359
|
+
if (t.isReturnStatement(n) && n.expression !== void 0)
|
|
2360
|
+
out.push(n.expression);
|
|
2361
|
+
t.forEachChild(n, visit);
|
|
2362
|
+
};
|
|
2363
|
+
t.forEachChild(fn.body, visit);
|
|
2364
|
+
return out;
|
|
2365
|
+
}
|
|
2366
|
+
function subtractingComparator(call, ctx) {
|
|
2367
|
+
const t = ctx.ts;
|
|
2368
|
+
const callee = call.expression;
|
|
2369
|
+
if (!t.isPropertyAccessExpression(callee) || callee.name.text !== "sort" && callee.name.text !== "toSorted")
|
|
2370
|
+
return void 0;
|
|
2371
|
+
const fn = call.arguments[0];
|
|
2372
|
+
if (fn === void 0 || !t.isArrowFunction(fn) && !t.isFunctionExpression(fn))
|
|
2373
|
+
return void 0;
|
|
2374
|
+
for (const r of returnedExpressions(fn, t)) {
|
|
2375
|
+
const e = skipValueWrappers(r, t);
|
|
2376
|
+
if (t.isBinaryExpression(e) && e.operatorToken.kind === t.SyntaxKind.MinusToken && (isEastValueExpression(e.left, ctx) || isEastValueExpression(e.right, ctx))) {
|
|
2377
|
+
return e;
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
return void 0;
|
|
2381
|
+
}
|
|
2006
2382
|
var noHostComparisonOnEastValues = {
|
|
2007
2383
|
name: NAME24,
|
|
2008
2384
|
code: CODE24,
|
|
2009
|
-
description: "Flag ===/!==/</> on decoded East values
|
|
2385
|
+
description: "Flag ===/!==/</> and subtracting sort comparators on decoded East values \u2014 use equalFor(T) / compareFor(T).",
|
|
2010
2386
|
check(node, ctx) {
|
|
2011
2387
|
const t = ctx.ts;
|
|
2388
|
+
const sf = ctx.sourceFile;
|
|
2389
|
+
if (t.isCallExpression(node)) {
|
|
2390
|
+
if (!importsEastPackage(sf, t))
|
|
2391
|
+
return;
|
|
2392
|
+
const subtraction = subtractingComparator(node, ctx);
|
|
2393
|
+
if (subtraction === void 0)
|
|
2394
|
+
return;
|
|
2395
|
+
const start2 = subtraction.getStart(sf);
|
|
2396
|
+
ctx.report({
|
|
2397
|
+
ruleName: NAME24,
|
|
2398
|
+
code: CODE24,
|
|
2399
|
+
start: start2,
|
|
2400
|
+
length: subtraction.getEnd() - start2,
|
|
2401
|
+
messageText: "A comparator that subtracts East values mis-orders them \u2014 a `NaN` makes it inconsistent and an Integer past 2^53 rounds. Order with `compareFor(T)` (`xs.sort((a, b) => compare(a.v, b.v))`).",
|
|
2402
|
+
category: "warning"
|
|
2403
|
+
});
|
|
2404
|
+
return;
|
|
2405
|
+
}
|
|
2012
2406
|
if (!t.isBinaryExpression(node))
|
|
2013
2407
|
return;
|
|
2014
2408
|
const k = t.SyntaxKind;
|
|
@@ -2017,22 +2411,20 @@ var noHostComparisonOnEastValues = {
|
|
|
2017
2411
|
const relational = op === k.LessThanToken || op === k.LessThanEqualsToken || op === k.GreaterThanToken || op === k.GreaterThanEqualsToken;
|
|
2018
2412
|
if (!equality && !relational)
|
|
2019
2413
|
return;
|
|
2020
|
-
if (!importsEastPackage(
|
|
2414
|
+
if (!importsEastPackage(sf, t))
|
|
2021
2415
|
return;
|
|
2022
2416
|
if (isNullish(node.left, t) || isNullish(node.right, t))
|
|
2023
2417
|
return;
|
|
2024
|
-
const
|
|
2025
|
-
|
|
2026
|
-
if (!leftShaped && !rightShaped)
|
|
2418
|
+
const flagged = equality ? isEastObject(node.left, ctx) || isEastObject(node.right, ctx) : isEastValueShape(ctx.checker.getTypeAtLocation(node.left), ctx) || isEastValueShape(ctx.checker.getTypeAtLocation(node.right), ctx);
|
|
2419
|
+
if (!flagged)
|
|
2027
2420
|
return;
|
|
2028
|
-
const sf = ctx.sourceFile;
|
|
2029
2421
|
const start = node.getStart(sf);
|
|
2030
2422
|
ctx.report({
|
|
2031
2423
|
ruleName: NAME24,
|
|
2032
2424
|
code: CODE24,
|
|
2033
2425
|
start,
|
|
2034
2426
|
length: node.getEnd() - start,
|
|
2035
|
-
messageText: equality ? "Host equality on a decoded East value compares object identity \u2014 two equal variants are never `===`. Use `equalFor(T)(a, b)
|
|
2427
|
+
messageText: equality ? "Host equality on a decoded East value compares object identity \u2014 two equal structs, variants, DateTimes or Sets are never `===`. Use `equalFor(T)(a, b)`, or `Object.is(a, b)` where you mean the same object." : "Host ordering on a decoded East value compares the wrong representation. Use `compareFor(T)` / `lessFor(T)` (e.g. `arr.sort(compareFor(T))`).",
|
|
2036
2428
|
category: "warning"
|
|
2037
2429
|
});
|
|
2038
2430
|
}
|
|
@@ -2240,6 +2632,227 @@ var noInlineCredentials = {
|
|
|
2240
2632
|
}
|
|
2241
2633
|
};
|
|
2242
2634
|
|
|
2635
|
+
// ../east-diagnostics/dist/src/rules/no-host-print-of-east-values.js
|
|
2636
|
+
var NAME28 = "no-host-print-of-east-values";
|
|
2637
|
+
var CODE28 = 990033;
|
|
2638
|
+
var PRINTING_METHODS = /* @__PURE__ */ new Set(["toString", "toISOString", "toJSON"]);
|
|
2639
|
+
var CSS_UNIT = /^(?:px|r?em|ch|fr|deg|turn|[sdl]?v(?:h|w|min|max))(?![A-Za-z])/;
|
|
2640
|
+
function writesCssLength(span, ctx) {
|
|
2641
|
+
if (!CSS_UNIT.test(span.literal.text))
|
|
2642
|
+
return false;
|
|
2643
|
+
const type = ctx.checker.getTypeAtLocation(span.expression);
|
|
2644
|
+
const members = type.isUnion() ? type.types : [type];
|
|
2645
|
+
return members.every((m) => (m.flags & ctx.ts.TypeFlags.NumberLike) !== 0);
|
|
2646
|
+
}
|
|
2647
|
+
function printedValue(node, ctx) {
|
|
2648
|
+
const t = ctx.ts;
|
|
2649
|
+
if (t.isCallExpression(node)) {
|
|
2650
|
+
const callee = node.expression;
|
|
2651
|
+
if (t.isIdentifier(callee) && callee.text === "String" && node.arguments.length === 1 && isGlobalBuiltin(callee, ctx)) {
|
|
2652
|
+
return [node.arguments[0]];
|
|
2653
|
+
}
|
|
2654
|
+
if (t.isPropertyAccessExpression(callee)) {
|
|
2655
|
+
if (t.isIdentifier(callee.expression) && callee.expression.text === "JSON" && callee.name.text === "stringify" && node.arguments.length > 0 && isGlobalBuiltin(callee.expression, ctx)) {
|
|
2656
|
+
return [node.arguments[0]];
|
|
2657
|
+
}
|
|
2658
|
+
if (PRINTING_METHODS.has(callee.name.text))
|
|
2659
|
+
return [callee.expression];
|
|
2660
|
+
}
|
|
2661
|
+
return [];
|
|
2662
|
+
}
|
|
2663
|
+
if (t.isTemplateExpression(node) && !t.isTaggedTemplateExpression(node.parent)) {
|
|
2664
|
+
return node.templateSpans.filter((span) => !writesCssLength(span, ctx)).map((span) => span.expression);
|
|
2665
|
+
}
|
|
2666
|
+
if (t.isBinaryExpression(node) && node.operatorToken.kind === t.SyntaxKind.PlusToken) {
|
|
2667
|
+
const stringy = (e) => (ctx.checker.getTypeAtLocation(e).flags & t.TypeFlags.StringLike) !== 0;
|
|
2668
|
+
if (stringy(node.left))
|
|
2669
|
+
return [node.right];
|
|
2670
|
+
if (stringy(node.right))
|
|
2671
|
+
return [node.left];
|
|
2672
|
+
}
|
|
2673
|
+
return [];
|
|
2674
|
+
}
|
|
2675
|
+
var noHostPrintOfEastValues = {
|
|
2676
|
+
name: NAME28,
|
|
2677
|
+
code: CODE28,
|
|
2678
|
+
description: "Flag String()/toString()/toISOString()/JSON.stringify/template printing of a decoded East value JavaScript prints differently \u2014 use printFor(T) or the locale Formatters.",
|
|
2679
|
+
check(node, ctx) {
|
|
2680
|
+
const t = ctx.ts;
|
|
2681
|
+
if (!t.isCallExpression(node) && !t.isTemplateExpression(node) && !t.isBinaryExpression(node))
|
|
2682
|
+
return;
|
|
2683
|
+
if (!importsEastPackage(ctx.sourceFile, t))
|
|
2684
|
+
return;
|
|
2685
|
+
for (const value of printedValue(node, ctx)) {
|
|
2686
|
+
if (!jsPrintDiffers(ctx.checker.getTypeAtLocation(value), t))
|
|
2687
|
+
continue;
|
|
2688
|
+
if (!isEastValueExpression(value, ctx))
|
|
2689
|
+
continue;
|
|
2690
|
+
const sf = ctx.sourceFile;
|
|
2691
|
+
const start = value.getStart(sf);
|
|
2692
|
+
ctx.report({
|
|
2693
|
+
ruleName: NAME28,
|
|
2694
|
+
code: CODE28,
|
|
2695
|
+
start,
|
|
2696
|
+
length: value.getEnd() - start,
|
|
2697
|
+
messageText: "JavaScript prints this East value its own way \u2014 `5` for East's `5.0`, a DateTime in local time or with a `Z`, `[object Object]` for a struct. Print it with `printFor(T)`, or in the viewer's language with the locale `Formatters`.",
|
|
2698
|
+
category: "warning"
|
|
2699
|
+
});
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
};
|
|
2703
|
+
|
|
2704
|
+
// ../east-diagnostics/dist/src/rules/no-host-parse-to-east-values.js
|
|
2705
|
+
var NAME29 = "no-host-parse-to-east-values";
|
|
2706
|
+
var CODE29 = 990034;
|
|
2707
|
+
var PARSERS = /* @__PURE__ */ new Set(["BigInt", "Number", "parseFloat", "parseInt"]);
|
|
2708
|
+
function isText(e, ctx) {
|
|
2709
|
+
return (ctx.checker.getTypeAtLocation(e).flags & ctx.ts.TypeFlags.StringLike) !== 0;
|
|
2710
|
+
}
|
|
2711
|
+
function literalText(e, t) {
|
|
2712
|
+
return t.isStringLiteral(e) || t.isNoSubstitutionTemplateLiteral(e) ? e.text : void 0;
|
|
2713
|
+
}
|
|
2714
|
+
var ZONELESS_DATE_TIME = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?$/;
|
|
2715
|
+
function parsesText(node, ctx) {
|
|
2716
|
+
const t = ctx.ts;
|
|
2717
|
+
const misread = (text, date) => {
|
|
2718
|
+
if (!isText(text, ctx))
|
|
2719
|
+
return false;
|
|
2720
|
+
const literal = literalText(text, t);
|
|
2721
|
+
return literal === void 0 || date && ZONELESS_DATE_TIME.test(literal);
|
|
2722
|
+
};
|
|
2723
|
+
if (t.isCallExpression(node)) {
|
|
2724
|
+
const callee = node.expression;
|
|
2725
|
+
const text = node.arguments[0];
|
|
2726
|
+
if (text === void 0)
|
|
2727
|
+
return false;
|
|
2728
|
+
if (t.isIdentifier(callee))
|
|
2729
|
+
return PARSERS.has(callee.text) && isGlobalBuiltin(callee, ctx) && misread(text, false);
|
|
2730
|
+
if (!t.isPropertyAccessExpression(callee) || !t.isIdentifier(callee.expression) || !isGlobalBuiltin(callee.expression, ctx))
|
|
2731
|
+
return false;
|
|
2732
|
+
if (callee.expression.text === "JSON" && callee.name.text === "parse")
|
|
2733
|
+
return misread(text, false);
|
|
2734
|
+
if (callee.expression.text === "Date" && callee.name.text === "parse")
|
|
2735
|
+
return misread(text, true);
|
|
2736
|
+
return false;
|
|
2737
|
+
}
|
|
2738
|
+
if (t.isNewExpression(node) && t.isIdentifier(node.expression) && node.expression.text === "Date") {
|
|
2739
|
+
const text = node.arguments?.[0];
|
|
2740
|
+
return node.arguments?.length === 1 && text !== void 0 && isGlobalBuiltin(node.expression, ctx) && misread(text, true);
|
|
2741
|
+
}
|
|
2742
|
+
return false;
|
|
2743
|
+
}
|
|
2744
|
+
var noHostParseToEastValues = {
|
|
2745
|
+
name: NAME29,
|
|
2746
|
+
code: CODE29,
|
|
2747
|
+
description: "Flag BigInt()/Number()/parseFloat()/parseInt()/JSON.parse()/new Date() reading text into an East slot \u2014 use parseFor(T) or East's datetime format.",
|
|
2748
|
+
check(node, ctx) {
|
|
2749
|
+
const t = ctx.ts;
|
|
2750
|
+
if (!t.isCallExpression(node) && !t.isNewExpression(node))
|
|
2751
|
+
return;
|
|
2752
|
+
if (!importsEastPackage(ctx.sourceFile, t))
|
|
2753
|
+
return;
|
|
2754
|
+
if (!parsesText(node, ctx))
|
|
2755
|
+
return;
|
|
2756
|
+
if (!landsInEastSlot(node, ctx))
|
|
2757
|
+
return;
|
|
2758
|
+
const sf = ctx.sourceFile;
|
|
2759
|
+
const start = node.getStart(sf);
|
|
2760
|
+
ctx.report({
|
|
2761
|
+
ruleName: NAME29,
|
|
2762
|
+
code: CODE29,
|
|
2763
|
+
start,
|
|
2764
|
+
length: node.getEnd() - start,
|
|
2765
|
+
messageText: 'A JavaScript parser reads this text into an East value wrongly \u2014 `BigInt("")` is 0, `BigInt("0x10")` is 16, `new Date(text)` reads local time. Read it with `parseFor(T)` (or East\'s datetime format) and act on its `success`.',
|
|
2766
|
+
category: "warning"
|
|
2767
|
+
});
|
|
2768
|
+
}
|
|
2769
|
+
};
|
|
2770
|
+
|
|
2771
|
+
// ../east-diagnostics/dist/src/rules/no-js-type-dispatch-on-east-values.js
|
|
2772
|
+
var NAME30 = "no-js-type-dispatch-on-east-values";
|
|
2773
|
+
var CODE30 = 990035;
|
|
2774
|
+
var noJsTypeDispatchOnEastValues = {
|
|
2775
|
+
name: NAME30,
|
|
2776
|
+
code: CODE30,
|
|
2777
|
+
description: "Flag typeof / instanceof on a decoded East value \u2014 dispatch on its East type (isValueOf) or its variant tag.",
|
|
2778
|
+
check(node, ctx) {
|
|
2779
|
+
const t = ctx.ts;
|
|
2780
|
+
let target;
|
|
2781
|
+
if (t.isTypeOfExpression(node))
|
|
2782
|
+
target = node.expression;
|
|
2783
|
+
else if (t.isBinaryExpression(node) && node.operatorToken.kind === t.SyntaxKind.InstanceOfKeyword)
|
|
2784
|
+
target = node.left;
|
|
2785
|
+
if (target === void 0)
|
|
2786
|
+
return;
|
|
2787
|
+
if (!importsEastPackage(ctx.sourceFile, t))
|
|
2788
|
+
return;
|
|
2789
|
+
if (!isEastValueExpression(target, ctx))
|
|
2790
|
+
return;
|
|
2791
|
+
const sf = ctx.sourceFile;
|
|
2792
|
+
const start = node.getStart(sf);
|
|
2793
|
+
ctx.report({
|
|
2794
|
+
ruleName: NAME30,
|
|
2795
|
+
code: CODE30,
|
|
2796
|
+
start,
|
|
2797
|
+
length: node.getEnd() - start,
|
|
2798
|
+
messageText: "`typeof` / `instanceof` asks JavaScript what this East value is, and loses its East type. Dispatch on its East type (`isValueOf(v, T)`, or the type it already has) or on its variant tag (`v.type`).",
|
|
2799
|
+
category: "warning"
|
|
2800
|
+
});
|
|
2801
|
+
}
|
|
2802
|
+
};
|
|
2803
|
+
|
|
2804
|
+
// ../east-diagnostics/dist/src/rules/no-js-collection-for-east-collection.js
|
|
2805
|
+
var NAME31 = "no-js-collection-for-east-collection";
|
|
2806
|
+
var CODE31 = 990036;
|
|
2807
|
+
function keyTypeOf(node, ctx) {
|
|
2808
|
+
const t = ctx.ts;
|
|
2809
|
+
const type = ctx.checker.getTypeAtLocation(node);
|
|
2810
|
+
if ((type.flags & t.TypeFlags.Object) === 0 || (type.objectFlags & t.ObjectFlags.Reference) === 0)
|
|
2811
|
+
return void 0;
|
|
2812
|
+
return ctx.checker.getTypeArguments(type)[0];
|
|
2813
|
+
}
|
|
2814
|
+
function keyedByIdentity(type, t) {
|
|
2815
|
+
const primitive = t.TypeFlags.StringLike | t.TypeFlags.NumberLike | t.TypeFlags.BigIntLike | t.TypeFlags.BooleanLike | t.TypeFlags.EnumLike | t.TypeFlags.ESSymbolLike | t.TypeFlags.Null | t.TypeFlags.Undefined;
|
|
2816
|
+
const members = type.isUnion() ? type.types : [type];
|
|
2817
|
+
return members.some((m) => {
|
|
2818
|
+
if (m.isIntersection())
|
|
2819
|
+
return m.types.every((part) => (part.flags & primitive) === 0);
|
|
2820
|
+
return (m.flags & (t.TypeFlags.Object | t.TypeFlags.NonPrimitive)) !== 0;
|
|
2821
|
+
});
|
|
2822
|
+
}
|
|
2823
|
+
var noJsCollectionForEastCollection = {
|
|
2824
|
+
name: NAME31,
|
|
2825
|
+
code: CODE31,
|
|
2826
|
+
description: "Flag new Set()/new Map() of object keys (structs, variants, DateTimes, Blobs) landing in an East Set or Dict \u2014 build SortedSet/SortedMap with compareFor(K).",
|
|
2827
|
+
check(node, ctx) {
|
|
2828
|
+
const t = ctx.ts;
|
|
2829
|
+
if (!t.isNewExpression(node) || !t.isIdentifier(node.expression))
|
|
2830
|
+
return;
|
|
2831
|
+
const kind = node.expression.text;
|
|
2832
|
+
if (kind !== "Set" && kind !== "Map")
|
|
2833
|
+
return;
|
|
2834
|
+
if (!importsEastPackage(ctx.sourceFile, t))
|
|
2835
|
+
return;
|
|
2836
|
+
if (!isGlobalBuiltin(node.expression, ctx))
|
|
2837
|
+
return;
|
|
2838
|
+
const key = keyTypeOf(node, ctx);
|
|
2839
|
+
if (key === void 0 || !keyedByIdentity(key, t))
|
|
2840
|
+
return;
|
|
2841
|
+
if (!landsInEastSlot(node, ctx))
|
|
2842
|
+
return;
|
|
2843
|
+
const sf = ctx.sourceFile;
|
|
2844
|
+
const start = node.getStart(sf);
|
|
2845
|
+
ctx.report({
|
|
2846
|
+
ruleName: NAME31,
|
|
2847
|
+
code: CODE31,
|
|
2848
|
+
start,
|
|
2849
|
+
length: node.getEnd() - start,
|
|
2850
|
+
messageText: kind === "Set" ? "A JavaScript `Set` finds a struct, variant, DateTime or Blob member by identity, so an equal value built elsewhere misses it. Build `new SortedSet(values, compareFor(K))`." : "A JavaScript `Map` finds a struct, variant, DateTime or Blob key by identity, so an equal key built elsewhere misses its entry. Build `new SortedMap(entries, compareFor(K))`.",
|
|
2851
|
+
category: "warning"
|
|
2852
|
+
});
|
|
2853
|
+
}
|
|
2854
|
+
};
|
|
2855
|
+
|
|
2243
2856
|
// ../east-diagnostics/dist/src/rules/index.js
|
|
2244
2857
|
var allRules = [
|
|
2245
2858
|
// East-side idiom hygiene (original set)
|
|
@@ -2274,7 +2887,14 @@ var allRules = [
|
|
|
2274
2887
|
requireExampleReturns,
|
|
2275
2888
|
noDuplicateDefinitionName,
|
|
2276
2889
|
// secrets hygiene: IR is content-addressed and replicated — no literal creds
|
|
2277
|
-
noInlineCredentials
|
|
2890
|
+
noInlineCredentials,
|
|
2891
|
+
// host code holding DECODED East values: print, read, dispatch and collect
|
|
2892
|
+
// them through East (#963) — with noHostComparisonOnEastValues and
|
|
2893
|
+
// noHandrolledValueTypeMirror above
|
|
2894
|
+
noHostPrintOfEastValues,
|
|
2895
|
+
noHostParseToEastValues,
|
|
2896
|
+
noJsTypeDispatchOnEastValues,
|
|
2897
|
+
noJsCollectionForEastCollection
|
|
2278
2898
|
];
|
|
2279
2899
|
|
|
2280
2900
|
// ../east-diagnostics/dist/src/run.js
|
|
@@ -2291,7 +2911,8 @@ function runEastRules(tsModule, program, sourceFile, checker, options = {}, rule
|
|
|
2291
2911
|
report: (d) => diagnostics.push(d)
|
|
2292
2912
|
};
|
|
2293
2913
|
const disabled = new Set(options.disabled ?? []);
|
|
2294
|
-
const
|
|
2914
|
+
const only = options.only === void 0 ? void 0 : new Set(options.only);
|
|
2915
|
+
const active = rules.filter((rule) => !disabled.has(rule.name) && (only === void 0 || only.has(rule.name)));
|
|
2295
2916
|
const visit = (node) => {
|
|
2296
2917
|
for (const rule of active)
|
|
2297
2918
|
rule.check(node, ctx);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elaraai/tsserver-plugin-east",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.81",
|
|
4
4
|
"license": "AGPL-3.0-or-later",
|
|
5
5
|
"description": "TypeScript language service plugin for East — East idiom diagnostics and localized East type-diff error messages as native editor squiggles.",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -32,8 +32,8 @@
|
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"esbuild": "^0.27.3",
|
|
34
34
|
"typescript": "~5.9.2",
|
|
35
|
-
"@elaraai/east": "1.0.
|
|
36
|
-
"@elaraai/east-diagnostics": "1.0.
|
|
35
|
+
"@elaraai/east": "1.0.81",
|
|
36
|
+
"@elaraai/east-diagnostics": "1.0.81"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "node scripts/build.mjs",
|