@lucidkiln/language-server 1.1.0 → 1.2.0
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/main.js +159 -96
- package/dist/main.js.map +1 -1
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -411,7 +411,7 @@ const splitParameters = (list) => {
|
|
|
411
411
|
const DOCS = {
|
|
412
412
|
tags: {
|
|
413
413
|
"add": {
|
|
414
|
-
"markdown": "- Content goes to the outlet with the same name, in the order it was added. It doesn't matter whether `add to` runs before or after the outlet.\n- The content
|
|
414
|
+
"markdown": "- Content goes to the outlet with the same name, in the order it was added. It doesn't matter whether `add to` runs before or after the outlet.\n- The content can read the variables around it. Variables you `set` inside it stay inside it.\n- Content for an outlet the page doesn't have is dropped. A component still works on a page without that outlet, and when you [render one block](https://kilnlang.com/docs/api/rendering/#rendering-one-block).",
|
|
415
415
|
"url": "https://kilnlang.com/docs/tags/outlet/#adding-content"
|
|
416
416
|
},
|
|
417
417
|
"apply": {
|
|
@@ -1412,19 +1412,26 @@ const bindingsAt = (scopes, offset) => {
|
|
|
1412
1412
|
*
|
|
1413
1413
|
* - `Get`: `a.b` and `a[0]`, reading a property, a map's value or a list's item.
|
|
1414
1414
|
* - `Item`, `Key`: what `for` gives for each pass.
|
|
1415
|
-
* - `
|
|
1415
|
+
* - `Params` and `Returns` describe a function for hover. A function made with `withRender` is an object whose `call`
|
|
1416
|
+
* takes `RenderInfo` first, so `Params` leaves that out.
|
|
1417
|
+
* - `arg`, `plain`, `aware`, `dropRender`, `Called`: a call to a host function, written as a real call so TypeScript
|
|
1418
|
+
* works out a generic function's type parameters from the arguments. See `typeWriter`.
|
|
1416
1419
|
* - `Members`: the values whose properties a template can read, so lists, maps and primitives complete nothing.
|
|
1417
1420
|
* - `Last`: a function's last parameter, which named arguments are gathered into.
|
|
1418
1421
|
* - `Fits`, `NamedFit`: whether an argument fits its parameter. Unknown types always fit, so the editor never guesses.
|
|
1419
1422
|
*/
|
|
1420
|
-
const PRELUDE = `import type { KilnFilters, KilnFunctions, KilnGlobals, KilnTests, KilnTypes, Markup
|
|
1423
|
+
const PRELUDE = `import type { KilnFilters, KilnFunctions, KilnGlobals, KilnTests, KilnTypes, Markup } from "@lucidkiln/core";
|
|
1421
1424
|
type Named<N extends string> = N extends keyof KilnTypes ? KilnTypes[N] : unknown;
|
|
1422
1425
|
type Get<V, K> = NonNullable<V> extends infer O ? O extends ReadonlyMap<unknown, infer M> ? M : O extends readonly (infer I)[] ? (K extends number ? I : unknown) : K extends keyof O ? O[K] : unknown : never;
|
|
1423
1426
|
type Item<V> = NonNullable<V> extends infer O ? O extends string ? unknown : O extends readonly (infer I)[] ? I : O extends ReadonlyMap<unknown, infer M> ? M : O extends ReadonlySet<infer S> ? S : O extends Iterable<infer I> ? I : O extends object ? O[keyof O] : unknown : never;
|
|
1424
1427
|
type Key<V> = NonNullable<V> extends infer O ? O extends string ? unknown : O extends readonly unknown[] | ReadonlySet<unknown> ? number : O extends ReadonlyMap<infer K, unknown> ? K : O extends object ? keyof O & string : unknown : never;
|
|
1425
|
-
type Returns<F> = F extends
|
|
1426
|
-
type
|
|
1427
|
-
|
|
1428
|
+
type Returns<F> = F extends (...args: never) => infer R ? R : F extends { readonly call: (...args: never) => infer R } ? R : unknown;
|
|
1429
|
+
type Params<F> = F extends (...args: infer A) => unknown ? A : F extends { readonly call: (render: never, ...args: infer A) => unknown } ? A : never;
|
|
1430
|
+
declare function arg<T>(): T;
|
|
1431
|
+
declare function plain<F>(fn: F): F extends (...args: never) => unknown ? F : never;
|
|
1432
|
+
declare function aware<F>(fn: F): F extends (...args: never) => unknown ? never : F extends { readonly call: infer C } ? C : never;
|
|
1433
|
+
declare function dropRender<A extends unknown[], R>(fn: (render: never, ...args: A) => R): (...args: A) => R;
|
|
1434
|
+
type Called<F, Plain, Aware> = unknown extends F ? unknown : F extends (...args: never) => unknown ? Awaited<Plain> : F extends { readonly call: unknown } ? Awaited<Aware> : unknown;
|
|
1428
1435
|
type Tail<A> = A extends readonly [unknown?, ...infer R] ? R : A;
|
|
1429
1436
|
type Last<A> = Required<A> extends readonly [...unknown[], infer L] ? L : A extends readonly (infer E)[] ? E : unknown;
|
|
1430
1437
|
type Fits<A, P> = [P] extends [never] ? true : unknown extends P ? true : unknown extends A ? true : [Exclude<A, Markup>] extends [P] ? true : false;
|
|
@@ -1513,88 +1520,135 @@ const ITEM_PARAMS = {
|
|
|
1513
1520
|
reduce: [1]
|
|
1514
1521
|
};
|
|
1515
1522
|
const MAX_DEPTH = 32;
|
|
1516
|
-
/** The TypeScript type of a variable, from where its value comes from. */
|
|
1517
|
-
const bindingType = (source, context, depth = 0) => {
|
|
1518
|
-
const of = (expression) => expressionType(expression, context, depth + 1);
|
|
1519
|
-
switch (source.kind) {
|
|
1520
|
-
case "prop": return propType(source.type);
|
|
1521
|
-
case "rest": return "Readonly<Record<string, unknown>>";
|
|
1522
|
-
case "set": return of(source.value);
|
|
1523
|
-
case "capture": return "Markup";
|
|
1524
|
-
case "item": return `Item<${of(source.iterable)}>`;
|
|
1525
|
-
case "key": return `Key<${of(source.iterable)}>`;
|
|
1526
|
-
case "arrow": return ITEM_PARAMS[source.filter]?.includes(source.index) ? `Item<${of(source.input)}>` : "unknown";
|
|
1527
|
-
case "with": return `Get<${of(source.value)}, ${JSON.stringify(source.key)}>`;
|
|
1528
|
-
default: return "unknown";
|
|
1529
|
-
}
|
|
1530
|
-
};
|
|
1531
1523
|
const union = (types) => types.length === 0 ? "never" : [...new Set(types)].join(" | ");
|
|
1532
1524
|
const literalKey = (expression) => expression.type === "Literal" && (typeof expression.value === "string" || typeof expression.value === "number") ? JSON.stringify(expression.value) : null;
|
|
1533
|
-
/**
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1525
|
+
/**
|
|
1526
|
+
* Writes the types of a template's values. A literal's type is its value, so `"page"` is `"page"`. A call to a host
|
|
1527
|
+
* function or filter becomes a real call in `statements`, so TypeScript works out a generic function's type parameters
|
|
1528
|
+
* from the arguments:
|
|
1529
|
+
*
|
|
1530
|
+
* ```ts
|
|
1531
|
+
* declare const $fn0: Get<KilnFunctions, "document">;
|
|
1532
|
+
* const $plain0 = plain($fn0)(arg<"page">());
|
|
1533
|
+
* const $aware0 = dropRender(aware($fn0))(arg<"page">());
|
|
1534
|
+
* ```
|
|
1535
|
+
*
|
|
1536
|
+
* The call's type is `Called<typeof $fn0, typeof $plain0, typeof $aware0>`: the first call for a plain function, the
|
|
1537
|
+
* second for one made with `withRender`. Each expression is written once, however often a template reads it.
|
|
1538
|
+
*/
|
|
1539
|
+
const typeWriter = (context) => {
|
|
1540
|
+
const statements = [];
|
|
1541
|
+
const written = /* @__PURE__ */ new Map();
|
|
1542
|
+
/** Writes a call to `callee`, a TypeScript type, with the types of its arguments, and returns the result's type. */
|
|
1543
|
+
const call = (callee, args) => {
|
|
1544
|
+
const id = statements.length;
|
|
1545
|
+
const list = args.join(", ");
|
|
1546
|
+
statements.push(`declare const $fn${id}: ${callee};\nconst $plain${id} = plain($fn${id})(${list});\nconst $aware${id} = dropRender(aware($fn${id}))(${list});\n`);
|
|
1547
|
+
return `Called<typeof $fn${id}, typeof $plain${id}, typeof $aware${id}>`;
|
|
1548
|
+
};
|
|
1549
|
+
/** A call's arguments as TypeScript: named arguments are gathered into one hash at the end, as Kiln does. */
|
|
1550
|
+
const callArgs = (list, depth, input) => {
|
|
1551
|
+
const named = list.flatMap((item) => item.type === "Named" ? [`${JSON.stringify(item.name)}: ${expression(item.value, depth)}`] : []);
|
|
1552
|
+
return [
|
|
1553
|
+
...input ? [`arg<${input}>()`] : [],
|
|
1554
|
+
...list.flatMap((item) => {
|
|
1555
|
+
if (item.type === "Named") return [];
|
|
1556
|
+
const type = expression(item.value, depth);
|
|
1557
|
+
return [item.type === "Spread" ? `...arg<Item<${type}>[]>()` : `arg<${type}>()`];
|
|
1558
|
+
}),
|
|
1559
|
+
...named.length > 0 ? [`arg<{ ${named.join("; ")} }>()`] : []
|
|
1560
|
+
];
|
|
1561
|
+
};
|
|
1562
|
+
const binding = (source, depth) => {
|
|
1563
|
+
const of = (value) => expression(value, depth + 1);
|
|
1564
|
+
switch (source.kind) {
|
|
1565
|
+
case "prop": return propType(source.type);
|
|
1566
|
+
case "rest": return "Readonly<Record<string, unknown>>";
|
|
1567
|
+
case "set": return of(source.value);
|
|
1568
|
+
case "capture": return "Markup";
|
|
1569
|
+
case "item": return `Item<${of(source.iterable)}>`;
|
|
1570
|
+
case "key": return `Key<${of(source.iterable)}>`;
|
|
1571
|
+
case "arrow": return ITEM_PARAMS[source.filter]?.includes(source.index) ? `Item<${of(source.input)}>` : "unknown";
|
|
1572
|
+
case "with": return `Get<${of(source.value)}, ${JSON.stringify(source.key)}>`;
|
|
1573
|
+
default: return "unknown";
|
|
1566
1574
|
}
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1575
|
+
};
|
|
1576
|
+
const expression = (node, depth) => {
|
|
1577
|
+
if (depth > MAX_DEPTH) return "unknown";
|
|
1578
|
+
const known = written.get(node);
|
|
1579
|
+
if (known) return known;
|
|
1580
|
+
const type = write(node, depth + 1);
|
|
1581
|
+
written.set(node, type);
|
|
1582
|
+
return type;
|
|
1583
|
+
};
|
|
1584
|
+
const write = (node, depth) => {
|
|
1585
|
+
const of = (inner) => expression(inner, depth);
|
|
1586
|
+
switch (node.type) {
|
|
1587
|
+
case "Literal": return typeof node.value === "string" ? JSON.stringify(node.value) : String(node.value);
|
|
1588
|
+
case "Interpolation": return "string";
|
|
1589
|
+
case "Array": return `(${union(node.elements.map((element) => element.type === "Spread" ? `Item<${of(element.value)}>` : of(element)))})[]`;
|
|
1590
|
+
case "Hash": return `{ ${node.entries.flatMap((entry) => {
|
|
1591
|
+
if (entry.type === "Spread") return [];
|
|
1592
|
+
const key = entry.key.type === "Name" ? JSON.stringify(entry.key.name) : literalKey(entry.key);
|
|
1593
|
+
return key ? [`${key}: ${of(entry.value)}`] : [];
|
|
1594
|
+
}).join("; ")} }`;
|
|
1595
|
+
case "Unary": return node.operator === "not" ? "boolean" : "number";
|
|
1596
|
+
case "Binary": switch (node.operator) {
|
|
1597
|
+
case "??":
|
|
1598
|
+
case "?:": return `NonNullable<${of(node.left)}> | ${of(node.right)}`;
|
|
1599
|
+
case "~": return "string";
|
|
1600
|
+
case "..": return "number[]";
|
|
1601
|
+
case "+":
|
|
1602
|
+
case "-":
|
|
1603
|
+
case "*":
|
|
1604
|
+
case "/":
|
|
1605
|
+
case "//":
|
|
1606
|
+
case "%":
|
|
1607
|
+
case "**": return "number";
|
|
1608
|
+
default: return "boolean";
|
|
1572
1609
|
}
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1610
|
+
case "Conditional": return union([of(node.consequent), node.alternate ? of(node.alternate) : "null"]);
|
|
1611
|
+
case "Member": {
|
|
1612
|
+
const key = literalKey(node.property) ?? "string";
|
|
1613
|
+
return `Get<${of(node.object)}, ${key}>`;
|
|
1614
|
+
}
|
|
1615
|
+
case "Call": {
|
|
1616
|
+
const { callee } = node;
|
|
1617
|
+
if (callee.type === "Member") {
|
|
1618
|
+
const key = literalKey(callee.property) ?? "string";
|
|
1619
|
+
return call(`Get<${of(callee.object)}, ${key}>`, callArgs(node.args, depth));
|
|
1620
|
+
}
|
|
1621
|
+
if (callee.type !== "Name") return "unknown";
|
|
1622
|
+
const name = callee.name;
|
|
1623
|
+
if (context.macros.has(name)) return "Markup";
|
|
1624
|
+
const builtin = BUILTIN_FUNCTIONS[name];
|
|
1625
|
+
if (builtin && !context.functions.includes(name)) return builtin;
|
|
1626
|
+
return call(`Get<KilnFunctions, ${JSON.stringify(name)}>`, callArgs(node.args, depth));
|
|
1627
|
+
}
|
|
1628
|
+
case "Filter": {
|
|
1629
|
+
const input = of(node.input);
|
|
1630
|
+
const builtin = BUILTIN_FILTERS[node.name];
|
|
1631
|
+
if (!builtin || context.filters.includes(node.name)) return call(`Get<KilnFilters, ${JSON.stringify(node.name)}>`, callArgs(node.args, depth, input));
|
|
1632
|
+
const arrow = node.args[0]?.value;
|
|
1633
|
+
if (node.name === "map" && arrow?.type === "Arrow") return `(${of(arrow.body)})[]`;
|
|
1634
|
+
return builtin(input, node.args.map((item) => item.type === "Spread" ? "unknown" : of(item.value)));
|
|
1635
|
+
}
|
|
1636
|
+
case "Test": return "boolean";
|
|
1637
|
+
case "OptionalChain": return of(node.expression);
|
|
1638
|
+
case "Name": {
|
|
1639
|
+
if (node.name === "loop") return "unknown";
|
|
1640
|
+
const found = bindingAt(context.scopes, node.name, node.span.start);
|
|
1641
|
+
if (found) return binding(found.source, depth);
|
|
1642
|
+
return `Get<KilnGlobals, ${JSON.stringify(node.name)}>`;
|
|
1643
|
+
}
|
|
1644
|
+
default: return "unknown";
|
|
1595
1645
|
}
|
|
1596
|
-
|
|
1597
|
-
|
|
1646
|
+
};
|
|
1647
|
+
return {
|
|
1648
|
+
expression: (node) => expression(node, 0),
|
|
1649
|
+
binding: (source) => binding(source, 0),
|
|
1650
|
+
statements
|
|
1651
|
+
};
|
|
1598
1652
|
};
|
|
1599
1653
|
/** The registries a project describes in TypeScript, by the kind of name the language server looks up. */
|
|
1600
1654
|
const REGISTRIES = {
|
|
@@ -1603,9 +1657,12 @@ const REGISTRIES = {
|
|
|
1603
1657
|
test: "KilnTests",
|
|
1604
1658
|
global: "KilnGlobals"
|
|
1605
1659
|
};
|
|
1606
|
-
/**
|
|
1607
|
-
|
|
1608
|
-
|
|
1660
|
+
/**
|
|
1661
|
+
* Checks a file of `type name = type` aliases with the project's TypeScript, and reads it with `use`. `statements` are
|
|
1662
|
+
* what the aliases read, from a `TypeWriter`.
|
|
1663
|
+
*/
|
|
1664
|
+
const withTypes = (typescript, aliases, use, statements = []) => {
|
|
1665
|
+
const source = PRELUDE + statements.join("") + Object.entries(aliases).map(([name, type]) => `type ${name} = ${type};\n`).join("");
|
|
1609
1666
|
return typescript.query(source, use);
|
|
1610
1667
|
};
|
|
1611
1668
|
/** The TypeScript type of a registered function, filter or test, by kind and name. */
|
|
@@ -1639,8 +1696,11 @@ const describeCallable = (typescript, kind, name) => withTypes(typescript, {
|
|
|
1639
1696
|
});
|
|
1640
1697
|
/** The names a function or filter takes as named arguments: the properties of its last parameter. */
|
|
1641
1698
|
const namedOptions = (typescript, kind, name) => withTypes(typescript, { Options: `Members<Last<${parametersType(kind, name)}>>` }, (read) => read.members("Options")) ?? [];
|
|
1642
|
-
/**
|
|
1643
|
-
|
|
1699
|
+
/**
|
|
1700
|
+
* Checks arguments against their parameters with one TypeScript query. Null for an argument that fits. `statements` are
|
|
1701
|
+
* what the argument types read, from a `TypeWriter`.
|
|
1702
|
+
*/
|
|
1703
|
+
const checkArguments = (typescript, checks, statements = []) => {
|
|
1644
1704
|
if (checks.length === 0) return [];
|
|
1645
1705
|
const aliases = {};
|
|
1646
1706
|
checks.forEach((check, index) => {
|
|
@@ -1658,7 +1718,7 @@ const checkArguments = (typescript, checks) => {
|
|
|
1658
1718
|
expected: read.describe(`Expected${index}`) ?? "unknown",
|
|
1659
1719
|
actual: read.describe(`Actual${index}`) ?? "unknown"
|
|
1660
1720
|
};
|
|
1661
|
-
})) ?? checks.map(() => null);
|
|
1721
|
+
}), statements) ?? checks.map(() => null);
|
|
1662
1722
|
};
|
|
1663
1723
|
//#endregion
|
|
1664
1724
|
//#region src/features.ts
|
|
@@ -1703,7 +1763,7 @@ const builtinItems = (builtins, docKind, kind, extra) => [...Object.entries(buil
|
|
|
1703
1763
|
kind,
|
|
1704
1764
|
detail: "From your project"
|
|
1705
1765
|
}))];
|
|
1706
|
-
/** Parses a template and finds its variables, with what `
|
|
1766
|
+
/** Parses a template and finds its variables, with what `typeWriter` needs to work out their types. */
|
|
1707
1767
|
const templateTypes = (open, text) => {
|
|
1708
1768
|
const manifest = open.project.manifest;
|
|
1709
1769
|
const scopes = templateScopes(text, (name) => name in TESTS || (manifest?.tests.includes(name) ?? false));
|
|
@@ -1739,8 +1799,9 @@ const memberDocs = (member) => [member.docs, ...member.tags.map((tag) => `*@${ta
|
|
|
1739
1799
|
const membersOf = async (open, object, context) => {
|
|
1740
1800
|
const typescript = await open.project.typescript();
|
|
1741
1801
|
if (!typescript) return [];
|
|
1742
|
-
const
|
|
1743
|
-
|
|
1802
|
+
const types = typeWriter(context);
|
|
1803
|
+
const type = types.expression(object);
|
|
1804
|
+
return withTypes(typescript, { Value: `Members<${type}>` }, (read) => read.members("Value"), types.statements) ?? [];
|
|
1744
1805
|
};
|
|
1745
1806
|
/** A function, filter, test or global the project describes in TypeScript. */
|
|
1746
1807
|
const registered = async (open, kind, name) => {
|
|
@@ -1962,7 +2023,8 @@ const hover = async (open, offset) => {
|
|
|
1962
2023
|
value = sections(codeBlock(prop ? describeProp(prop) : `...${symbol.name}`), BINDING_NOTES[binding.source.kind]);
|
|
1963
2024
|
} else if (binding) {
|
|
1964
2025
|
const typescript = await project.typescript();
|
|
1965
|
-
const
|
|
2026
|
+
const writer = typeWriter(types.context);
|
|
2027
|
+
const type = typescript ? withTypes(typescript, { Value: writer.binding(binding.source) }, (read) => read.describe("Value"), writer.statements) : null;
|
|
1966
2028
|
value = sections(codeBlock(type && type !== "unknown" ? `${symbol.name}: ${type}` : symbol.name, "ts"), BINDING_NOTES[binding.source.kind]);
|
|
1967
2029
|
} else {
|
|
1968
2030
|
const global = await registered(open, "global", symbol.name);
|
|
@@ -2263,6 +2325,7 @@ const argumentWarnings = async (open) => {
|
|
|
2263
2325
|
if (!typescript) return [];
|
|
2264
2326
|
const text = open.document.getText();
|
|
2265
2327
|
const { scopes, context } = templateTypes(open, text);
|
|
2328
|
+
const types = typeWriter(context);
|
|
2266
2329
|
/**
|
|
2267
2330
|
* Each check, with what it's for: a filter's input, a positional or a named argument. A wrong type is reported on
|
|
2268
2331
|
* the value, and a named argument the function doesn't take on its name.
|
|
@@ -2284,7 +2347,7 @@ const argumentWarnings = async (open) => {
|
|
|
2284
2347
|
const parameters = parametersType(kind, name, true);
|
|
2285
2348
|
[...node.type === "Filter" ? [node.input] : [], ...node.args.flatMap((arg) => arg.type === "Positional" ? [arg.value] : [])].forEach((value, index) => {
|
|
2286
2349
|
checks.push({
|
|
2287
|
-
type:
|
|
2350
|
+
type: types.expression(value),
|
|
2288
2351
|
parameters,
|
|
2289
2352
|
index,
|
|
2290
2353
|
span: value.span,
|
|
@@ -2295,7 +2358,7 @@ const argumentWarnings = async (open) => {
|
|
|
2295
2358
|
for (const arg of node.args) {
|
|
2296
2359
|
if (arg.type !== "Named") continue;
|
|
2297
2360
|
checks.push({
|
|
2298
|
-
type:
|
|
2361
|
+
type: types.expression(arg.value),
|
|
2299
2362
|
parameters,
|
|
2300
2363
|
name: arg.name,
|
|
2301
2364
|
span: arg.value.span,
|
|
@@ -2308,7 +2371,7 @@ const argumentWarnings = async (open) => {
|
|
|
2308
2371
|
});
|
|
2309
2372
|
}
|
|
2310
2373
|
}
|
|
2311
|
-
return checkArguments(typescript, checks).flatMap((problem, index) => {
|
|
2374
|
+
return checkArguments(typescript, checks, types.statements).flatMap((problem, index) => {
|
|
2312
2375
|
const check = checks[index];
|
|
2313
2376
|
if (!problem || !check) return [];
|
|
2314
2377
|
if (problem.kind === "missing") {
|