rip-lang 3.16.3 → 3.17.1
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/README.md +1 -1
- package/docs/dist/rip.js +707 -251
- package/docs/dist/rip.min.js +191 -191
- package/docs/dist/rip.min.js.br +0 -0
- package/docs/extensions/vscode/print/print-1.0.15.vsix +0 -0
- package/docs/extensions/vscode/print/print-latest.vsix +0 -0
- package/docs/extensions/vscode/rip/rip-latest.vsix +0 -0
- package/docs/extensions/vscode/rip/vscode-rip-0.7.1.vsix +0 -0
- package/package.json +1 -1
- package/src/compiler.js +19 -7
- package/src/dts.js +22 -15
- package/src/lexer.js +30 -1
- package/src/typecheck.js +164 -22
- package/src/types.js +463 -2
package/docs/dist/rip.min.js.br
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
package/src/compiler.js
CHANGED
|
@@ -1645,15 +1645,20 @@ export class CodeEmitter {
|
|
|
1645
1645
|
// no longer travels through the `.d.ts` header and the overload-interleave
|
|
1646
1646
|
// splice (typecheck.js). Off by default — runtime / `-c` output is unchanged.
|
|
1647
1647
|
//
|
|
1648
|
+
// `node` is the type-bearing s-expression head: the function NAME for `def`,
|
|
1649
|
+
// or the `->`/`=>` arrow operator for arrow functions (the lexer adapter
|
|
1650
|
+
// Object.assigns each token's `.data` — including `returnType` — onto its
|
|
1651
|
+
// String-wrapper, so both carry the annotation).
|
|
1652
|
+
//
|
|
1648
1653
|
// Only an EXPLICIT user annotation (`def f():: T`) is emitted, verbatim via
|
|
1649
1654
|
// `ripToTs`. `void` is deliberately NOT synthesized for `!` functions: on an
|
|
1650
1655
|
// async function `: void` (or any non-Promise type) is itself a TS1064, so a
|
|
1651
1656
|
// synthesized annotation would fire a spurious error on legitimate async
|
|
1652
1657
|
// side-effect functions once 1064 is recovered. Those keep flowing through
|
|
1653
1658
|
// the header until a later slice handles them.
|
|
1654
|
-
inlineReturnType(
|
|
1659
|
+
inlineReturnType(node) {
|
|
1655
1660
|
if (!this.options.inlineTypes) return '';
|
|
1656
|
-
let rt = meta(
|
|
1661
|
+
let rt = meta(node, 'returnType');
|
|
1657
1662
|
return rt ? `: ${ripToTs(rt)}` : '';
|
|
1658
1663
|
}
|
|
1659
1664
|
|
|
@@ -1665,7 +1670,8 @@ export class CodeEmitter {
|
|
|
1665
1670
|
let bodyCode = this.emitFunctionBody(body, params, sideEffectOnly);
|
|
1666
1671
|
let isAsync = this.containsAwait(body);
|
|
1667
1672
|
let isGen = this.containsYield(body);
|
|
1668
|
-
let
|
|
1673
|
+
let ret = this.inlineReturnType(sexpr?.[0]);
|
|
1674
|
+
let fn = `${isAsync ? 'async ' : ''}function${isGen ? '*' : ''}(${paramList})${ret} ${bodyCode}`;
|
|
1669
1675
|
return context === 'value' ? `(${fn})` : fn;
|
|
1670
1676
|
}
|
|
1671
1677
|
|
|
@@ -1674,7 +1680,13 @@ export class CodeEmitter {
|
|
|
1674
1680
|
if ((!params || (Array.isArray(params) && params.length === 0)) && this.containsIt(body)) params = ['it'];
|
|
1675
1681
|
let sideEffectOnly = sexpr.isVoid || false;
|
|
1676
1682
|
let paramList = this.emitParamList(params);
|
|
1677
|
-
|
|
1683
|
+
// RFC 12 phase 2: an inline return type forces parenthesized params —
|
|
1684
|
+
// `x: R => …` is invalid TS, only `(x): R => …`. Latent until fat-arrow
|
|
1685
|
+
// return types are expressible (the `=>` annotation-vs-operator grammar
|
|
1686
|
+
// ambiguity is a separate slice); `ret` is `''` today, so output is
|
|
1687
|
+
// unchanged.
|
|
1688
|
+
let ret = this.inlineReturnType(sexpr?.[0]);
|
|
1689
|
+
let isSingle = !ret && params.length === 1 && typeof params[0] === 'string' &&
|
|
1678
1690
|
!paramList.includes('=') && !paramList.includes('...') &&
|
|
1679
1691
|
!paramList.includes('[') && !paramList.includes('{');
|
|
1680
1692
|
let paramSyntax = isSingle ? paramList : `(${paramList})`;
|
|
@@ -1688,18 +1700,18 @@ export class CodeEmitter {
|
|
|
1688
1700
|
if (exprHead !== 'return' && !STMT_ONLY.has(exprHead)) {
|
|
1689
1701
|
let code = this.emit(expr, 'value');
|
|
1690
1702
|
if (code[0] === '{') code = `(${code})`;
|
|
1691
|
-
return `${prefix}${paramSyntax} => ${code}`;
|
|
1703
|
+
return `${prefix}${paramSyntax}${ret} => ${code}`;
|
|
1692
1704
|
}
|
|
1693
1705
|
}
|
|
1694
1706
|
if (!Array.isArray(body) || body[0] !== 'block') {
|
|
1695
1707
|
let code = this.emit(body, 'value');
|
|
1696
1708
|
if (code[0] === '{') code = `(${code})`;
|
|
1697
|
-
return `${prefix}${paramSyntax} => ${code}`;
|
|
1709
|
+
return `${prefix}${paramSyntax}${ret} => ${code}`;
|
|
1698
1710
|
}
|
|
1699
1711
|
}
|
|
1700
1712
|
|
|
1701
1713
|
let bodyCode = this.emitFunctionBody(body, params, sideEffectOnly);
|
|
1702
|
-
return `${prefix}${paramSyntax} => ${bodyCode}`;
|
|
1714
|
+
return `${prefix}${paramSyntax}${ret} => ${bodyCode}`;
|
|
1703
1715
|
}
|
|
1704
1716
|
|
|
1705
1717
|
emitReturn(head, rest, context, sexpr) {
|
package/src/dts.js
CHANGED
|
@@ -157,9 +157,11 @@ export function emitTypes(tokens, sexpr = null, source = '', schemaBehavior = nu
|
|
|
157
157
|
let dd = 0;
|
|
158
158
|
while (j < tokens.length) {
|
|
159
159
|
let dt = tokens[j];
|
|
160
|
-
if (dt[0] === '(' || dt[0] === '[' || dt[0] === '{'
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
if (dt[0] === '(' || dt[0] === '[' || dt[0] === '{' ||
|
|
161
|
+
dt[0] === 'CALL_START' || dt[0] === 'PARAM_START' || dt[0] === 'INDEX_START') dd++;
|
|
162
|
+
if (dt[0] === ')' || dt[0] === ']' || dt[0] === '}' ||
|
|
163
|
+
dt[0] === 'CALL_END' || dt[0] === 'PARAM_END' || dt[0] === 'INDEX_END') {
|
|
164
|
+
if (dd === 0) break; // closing bracket of the enclosing param list / pattern
|
|
163
165
|
dd--;
|
|
164
166
|
}
|
|
165
167
|
if (dd === 0 && dt[1] === ',') break;
|
|
@@ -348,29 +350,34 @@ export function emitTypes(tokens, sexpr = null, source = '', schemaBehavior = nu
|
|
|
348
350
|
continue;
|
|
349
351
|
}
|
|
350
352
|
|
|
351
|
-
// Destructured object parameter: { a, b }
|
|
353
|
+
// Destructured object parameter: { a, b }. An EXTERNAL type annotation
|
|
354
|
+
// `({a, b}: {a: T, b: U})` lands on the closing `}` token's data.type and
|
|
355
|
+
// takes precedence over a type reconstructed from in-pattern annotations.
|
|
352
356
|
if (tok[0] === '{') {
|
|
353
357
|
depth--; // undo the depth++ from nesting tracker above
|
|
354
358
|
let result = collectDestructuredObj(tokens, j);
|
|
355
359
|
j = result.endJ;
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
}
|
|
360
|
+
let ext = tokens[j - 1]?.data?.type;
|
|
361
|
+
let patternStr = result.patternStr;
|
|
362
|
+
// A whole-pattern default (`{…}: T = {}`) makes the param optional.
|
|
363
|
+
if (tokens[j]?.[0] === '=') { patternStr += '?'; j = skipDefault(tokens, j); }
|
|
364
|
+
if (ext) params.push(`${patternStr}: ${tsType(ext)}`);
|
|
365
|
+
else if (result.hasAnyType) params.push(`${patternStr}: ${result.typeStr}`);
|
|
366
|
+
else params.push(patternStr);
|
|
361
367
|
continue;
|
|
362
368
|
}
|
|
363
369
|
|
|
364
|
-
// Destructured array parameter: [ a, b ]
|
|
370
|
+
// Destructured array parameter: [ a, b ] (external type lands on `]`).
|
|
365
371
|
if (tok[0] === '[') {
|
|
366
372
|
depth--; // undo the depth++ from nesting tracker above
|
|
367
373
|
let result = collectDestructuredArr(tokens, j);
|
|
368
374
|
j = result.endJ;
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
}
|
|
375
|
+
let ext = tokens[j - 1]?.data?.type;
|
|
376
|
+
let patternStr = result.patternStr;
|
|
377
|
+
if (tokens[j]?.[0] === '=') { patternStr += '?'; j = skipDefault(tokens, j); }
|
|
378
|
+
if (ext) params.push(`${patternStr}: ${tsType(ext)}`);
|
|
379
|
+
else if (result.hasAnyType) params.push(`${patternStr}: ${result.typeStr}`);
|
|
380
|
+
else params.push(patternStr);
|
|
374
381
|
continue;
|
|
375
382
|
}
|
|
376
383
|
|
package/src/lexer.js
CHANGED
|
@@ -893,6 +893,13 @@ export class Lexer {
|
|
|
893
893
|
else if (tk[0] === 'SHIFT' && tk[1] === '>>>') depth += 3;
|
|
894
894
|
else if (tk[0] === 'COMPARE' && tk[1] === '<') depth--;
|
|
895
895
|
if (depth === 0 && tk[0] === 'TYPE_ANNOTATION') return false;
|
|
896
|
+
// A single `:` return type (Rip 3.17) — `…): Generic<…>` — closes a
|
|
897
|
+
// generic just like `::` does. Recognize it when the `:` directly
|
|
898
|
+
// follows a param-list close (mirrors the `tagParameters` acceptance).
|
|
899
|
+
if (depth === 0 && tk[0] === ':') {
|
|
900
|
+
let pv = this.tokens[k - 1];
|
|
901
|
+
if (pv && (pv[0] === ')' || pv[0] === 'PARAM_END' || pv[0] === 'CALL_END')) return false;
|
|
902
|
+
}
|
|
896
903
|
if (depth === 0 && tk[0] === 'IDENTIFIER' && tk[1] === 'type') return false;
|
|
897
904
|
if (tk[0] === 'INTERFACE') return false;
|
|
898
905
|
if (tk[0] === 'TERMINATOR' || tk[0] === 'INDENT' || tk[0] === 'OUTDENT') {
|
|
@@ -1534,7 +1541,10 @@ export class Lexer {
|
|
|
1534
1541
|
(tg === 'COMPARE' && tk[1] === '<')) {
|
|
1535
1542
|
depth--;
|
|
1536
1543
|
} else if (depth === 0) {
|
|
1537
|
-
|
|
1544
|
+
// A return-type annotation sits between the param `)` and the arrow.
|
|
1545
|
+
// Accept both `::` (TYPE_ANNOTATION) and a single `:` (Rip 3.17) as
|
|
1546
|
+
// the marker, but only when it directly follows the param-list `)`.
|
|
1547
|
+
if (tg === 'TYPE_ANNOTATION' || tg === ':') {
|
|
1538
1548
|
if (j > 0 && this.tokens[j - 1][0] === ')') found = j - 1;
|
|
1539
1549
|
break;
|
|
1540
1550
|
}
|
|
@@ -1545,6 +1555,25 @@ export class Lexer {
|
|
|
1545
1555
|
}
|
|
1546
1556
|
if (found < 0) return this.tagDoIife();
|
|
1547
1557
|
closeIdx = found;
|
|
1558
|
+
} else {
|
|
1559
|
+
// The token before the arrow is `)`. Normally that's the param-list
|
|
1560
|
+
// close — but it can also close a PARENTHESIZED return type:
|
|
1561
|
+
// `(x):: (R) =>`. Disambiguate by finding this `)`'s matching `(`; if a
|
|
1562
|
+
// `TYPE_ANNOTATION` (`::`) precedes that `(` and a `)` precedes the `::`,
|
|
1563
|
+
// this group is the return type and the real param close is that earlier
|
|
1564
|
+
// `)`. (A bare `(x) =>` has no `::` before its `(`, so it's unchanged.)
|
|
1565
|
+
let d = 0, op = -1;
|
|
1566
|
+
for (let k = closeIdx; k >= 0; k--) {
|
|
1567
|
+
let tg = this.tokens[k][0];
|
|
1568
|
+
if (tg === ')' || tg === 'CALL_END' || tg === 'PARAM_END') d++;
|
|
1569
|
+
else if (tg === '(' || tg === 'CALL_START' || tg === 'PARAM_START') {
|
|
1570
|
+
if (--d === 0) { op = k; break; }
|
|
1571
|
+
}
|
|
1572
|
+
}
|
|
1573
|
+
if (op > 1 && this.tokens[op - 1][0] === 'TYPE_ANNOTATION' &&
|
|
1574
|
+
this.tokens[op - 2]?.[0] === ')') {
|
|
1575
|
+
closeIdx = op - 2;
|
|
1576
|
+
}
|
|
1548
1577
|
}
|
|
1549
1578
|
|
|
1550
1579
|
let i = closeIdx;
|
package/src/typecheck.js
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// type errors mapped back to Rip source positions.
|
|
12
12
|
|
|
13
13
|
import { Compiler, getStdlibCode } from './compiler.js';
|
|
14
|
+
import { Lexer } from './lexer.js';
|
|
14
15
|
import { STDLIB_TYPE_DECLS } from './stdlib.js';
|
|
15
16
|
import { INTRINSIC_TYPE_DECLS, INTRINSIC_FN_DECL, ARIA_TYPE_DECLS, SIGNAL_INTERFACE, SIGNAL_FN, COMPUTED_INTERFACE, COMPUTED_FN, EFFECT_FN, ripDestructuredNames } from './dts.js';
|
|
16
17
|
import './schema/loader-server.js'; // registers full schema runtime provider
|
|
@@ -367,18 +368,31 @@ export function walkRoutesDir(routesDir) {
|
|
|
367
368
|
// Detect type annotations (:: followed by space or =) ignoring comments,
|
|
368
369
|
// string literals, and prototype syntax (Class::method).
|
|
369
370
|
export function hasTypeAnnotations(source) {
|
|
371
|
+
// Type aliases / interfaces don't lower to TYPE_ANNOTATION tokens, so detect
|
|
372
|
+
// them by name from the source first (cheap, and robust mid-edit).
|
|
370
373
|
let inHeredoc = false;
|
|
371
|
-
|
|
372
|
-
// Track heredoc boundaries (''' or """)
|
|
374
|
+
const hasTypeDecl = source.split('\n').some(line => {
|
|
373
375
|
const ticks = (line.match(/'''|"""/g) || []);
|
|
374
376
|
for (const t of ticks) inHeredoc = !inHeredoc;
|
|
375
377
|
if (inHeredoc) return false;
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
// Strip string literals (single and double quoted)
|
|
379
|
-
line = line.replace(/"(?:[^"\\]|\\.)*"/g, '""').replace(/'(?:[^'\\]|\\.)*'/g, "''");
|
|
380
|
-
return /::[ \t=]/.test(line) || /(?:^|export\s+)type\s+[A-Z]/.test(line.trimStart());
|
|
378
|
+
line = line.replace(/#.*$/, '').trimStart();
|
|
379
|
+
return /^(?:export\s+)?(?:type\s+[A-Z]|interface\s)/.test(line);
|
|
381
380
|
});
|
|
381
|
+
if (hasTypeDecl) return true;
|
|
382
|
+
// Type ANNOTATIONS — single-colon (Rip 3.17) or `::` — both lower to a
|
|
383
|
+
// TYPE_ANNOTATION token after the rewriter runs. Tokenizing is the accurate
|
|
384
|
+
// test (a string scan can't tell `x: T = v` from the object `{x: T}`); fall
|
|
385
|
+
// back to the literal `::` heuristic if tokenization fails on a partial buffer.
|
|
386
|
+
try {
|
|
387
|
+
// The rewriter consumes type annotations into `.data.type` /
|
|
388
|
+
// `.data.returnType` on the annotated token (they don't survive as
|
|
389
|
+
// TYPE_ANNOTATION tokens), so detect that metadata.
|
|
390
|
+
for (const t of new Lexer().tokenize(source, { rewrite: true }))
|
|
391
|
+
if (t.data && (t.data.type || t.data.returnType)) return true;
|
|
392
|
+
return false;
|
|
393
|
+
} catch {
|
|
394
|
+
return /::[ \t=]/.test(source.replace(/#.*$/gm, ''));
|
|
395
|
+
}
|
|
382
396
|
}
|
|
383
397
|
|
|
384
398
|
export function countLines(str) {
|
|
@@ -1153,6 +1167,82 @@ function replaceFnParams(line, newParams) {
|
|
|
1153
1167
|
return depth === 0 ? line.slice(0, idx + 1) + newParams + line.slice(i - 1) : line;
|
|
1154
1168
|
}
|
|
1155
1169
|
|
|
1170
|
+
// Skip a string/template/regex literal starting at `s[i]` (the opening quote),
|
|
1171
|
+
// returning the index just past the closing quote. Honors backslash escapes.
|
|
1172
|
+
function skipStringLiteral(s, i) {
|
|
1173
|
+
const q = s[i];
|
|
1174
|
+
i++;
|
|
1175
|
+
while (i < s.length) {
|
|
1176
|
+
if (s[i] === '\\') { i += 2; continue; }
|
|
1177
|
+
if (s[i] === q) return i + 1;
|
|
1178
|
+
i++;
|
|
1179
|
+
}
|
|
1180
|
+
return i;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
// Recognize a compiled module-scope fat-arrow assignment for `name`:
|
|
1184
|
+
// NAME = (params) => … NAME = ident => …
|
|
1185
|
+
// NAME = async (params) => … NAME = async ident => …
|
|
1186
|
+
// Returns null when `line` is not such an assignment, else
|
|
1187
|
+
// `{ hasInlineReturn }` — whether an inline `: T` return annotation already
|
|
1188
|
+
// sits between the params and `=>`. (There is no generator-arrow form; thin
|
|
1189
|
+
// arrows lower to `= function(` and are matched separately.) A structural
|
|
1190
|
+
// scan, not a regex: generated param lists can contain nested parens, default
|
|
1191
|
+
// values, and string literals, so the params span and the implementation `=>`
|
|
1192
|
+
// must be located by balanced, string-aware scanning.
|
|
1193
|
+
function analyzeArrowImpl(line, name) {
|
|
1194
|
+
const pre = new RegExp(`^\\s*${name}\\s*=\\s*`).exec(line);
|
|
1195
|
+
if (!pre) return null;
|
|
1196
|
+
const s = line;
|
|
1197
|
+
let i = pre[0].length;
|
|
1198
|
+
const skipWs = () => { while (i < s.length && (s[i] === ' ' || s[i] === '\t')) i++; };
|
|
1199
|
+
skipWs();
|
|
1200
|
+
// `async ` is the modifier — but only when real params follow. A bare
|
|
1201
|
+
// identifier param named `async` (`NAME = async => …`) is not the modifier;
|
|
1202
|
+
// don't consume it as one (it would otherwise mis-parse as no params).
|
|
1203
|
+
if (s.startsWith('async', i) && /\s/.test(s[i + 5] || '')) {
|
|
1204
|
+
let k = i + 5;
|
|
1205
|
+
while (k < s.length && (s[k] === ' ' || s[k] === '\t')) k++;
|
|
1206
|
+
if (!(s[k] === '=' && s[k + 1] === '>')) i = k;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
// Parameter list: `(...)` (balanced, string-aware) or a bare identifier.
|
|
1210
|
+
if (s[i] === '(') {
|
|
1211
|
+
let depth = 0;
|
|
1212
|
+
for (; i < s.length; i++) {
|
|
1213
|
+
const c = s[i];
|
|
1214
|
+
if (c === '"' || c === "'" || c === '`') { i = skipStringLiteral(s, i) - 1; continue; }
|
|
1215
|
+
if (c === '(') depth++;
|
|
1216
|
+
else if (c === ')' && --depth === 0) { i++; break; }
|
|
1217
|
+
}
|
|
1218
|
+
if (depth !== 0) return null;
|
|
1219
|
+
} else if (/[A-Za-z_$]/.test(s[i] || '')) {
|
|
1220
|
+
while (i < s.length && /[\w$]/.test(s[i])) i++;
|
|
1221
|
+
} else {
|
|
1222
|
+
return null;
|
|
1223
|
+
}
|
|
1224
|
+
skipWs();
|
|
1225
|
+
|
|
1226
|
+
// Optional inline return type `: T` between the params and `=>`. Latent
|
|
1227
|
+
// today (fat-arrow return types aren't expressible yet), but recognized so
|
|
1228
|
+
// the self-sufficiency check stays correct once they are.
|
|
1229
|
+
let hasInlineReturn = false;
|
|
1230
|
+
if (s[i] === ':') {
|
|
1231
|
+
hasInlineReturn = true;
|
|
1232
|
+
let depth = 0;
|
|
1233
|
+
for (; i < s.length; i++) {
|
|
1234
|
+
const c = s[i];
|
|
1235
|
+
if (c === '"' || c === "'" || c === '`') { i = skipStringLiteral(s, i) - 1; continue; }
|
|
1236
|
+
if (c === '=' && s[i + 1] === '>' && depth === 0) break;
|
|
1237
|
+
if (c === '(' || c === '[' || c === '{' || c === '<') depth++;
|
|
1238
|
+
else if (c === ')' || c === ']' || c === '}' || c === '>') depth--;
|
|
1239
|
+
}
|
|
1240
|
+
skipWs();
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
return (s[i] === '=' && s[i + 1] === '>') ? { hasInlineReturn } : null;
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1156
1246
|
// Depth-aware split of a parameter list on top-level commas. Respects
|
|
1157
1247
|
// nested parens, brackets, braces, and angle brackets so callback types
|
|
1158
1248
|
// like `(fn: (x: number) => void)` and generic types like `Map<K, V>`
|
|
@@ -1422,22 +1512,26 @@ export function compileForCheck(filePath, source, compiler, opts = {}) {
|
|
|
1422
1512
|
const injections = [];
|
|
1423
1513
|
const moved = new Set();
|
|
1424
1514
|
for (const fn of fnSigs) {
|
|
1425
|
-
// Match the impl in
|
|
1515
|
+
// Match the impl in one of three shapes:
|
|
1426
1516
|
// 1. top-level: `function NAME(`, `function NAME<`,
|
|
1427
1517
|
// `async function NAME(`, `export function NAME(`
|
|
1428
|
-
// 2.
|
|
1518
|
+
// 2. thin-arrow assignment: `NAME = function(`,
|
|
1429
1519
|
// `NAME = async function(`, `NAME = function*(`
|
|
1430
|
-
//
|
|
1431
|
-
//
|
|
1432
|
-
//
|
|
1433
|
-
//
|
|
1434
|
-
//
|
|
1435
|
-
//
|
|
1436
|
-
//
|
|
1520
|
+
// (`name = (...) -> ...` lowers to `name = function(...) {…}`)
|
|
1521
|
+
// 3. fat-arrow assignment: `NAME = (...) => …`, `NAME = x => …`,
|
|
1522
|
+
// `NAME = async (...) => …` (`name = (...) => ...`). Without this
|
|
1523
|
+
// the own-file `declare function NAME` survives next to the
|
|
1524
|
+
// `let NAME` / `NAME = … =>` binding and TS reports TS2630
|
|
1525
|
+
// ("cannot assign to NAME because it is a function") — visible in
|
|
1526
|
+
// the editor shadow even when CLI `rip check` masks it.
|
|
1527
|
+
// We deliberately do NOT match `obj.NAME = …`: the DTS emits
|
|
1528
|
+
// `declare function NAME(...)` only for module-scope, bare-name
|
|
1529
|
+
// assignments. A property-style match would pick up an unrelated
|
|
1530
|
+
// `obj.NAME = ...` line sharing the name and apply the wrong signature.
|
|
1437
1531
|
const topLevelPat = new RegExp(`^(?:export\\s+)?(?:async\\s+)?function\\s+${fn.name}\\s*[(<]`);
|
|
1438
1532
|
const arrowAssignPat = new RegExp(`^\\s*${fn.name}\\s*=\\s*(?:async\\s+)?function\\s*\\*?\\s*\\(`);
|
|
1439
1533
|
for (let j = 0; j < cl.length; j++) {
|
|
1440
|
-
if (topLevelPat.test(cl[j]) || arrowAssignPat.test(cl[j])) {
|
|
1534
|
+
if (topLevelPat.test(cl[j]) || arrowAssignPat.test(cl[j]) || analyzeArrowImpl(cl[j], fn.name)) {
|
|
1441
1535
|
injections.push({ codeLine: j, sig: fn.sig });
|
|
1442
1536
|
moved.add(fn.idx);
|
|
1443
1537
|
break;
|
|
@@ -1536,16 +1630,60 @@ export function compileForCheck(filePath, source, compiler, opts = {}) {
|
|
|
1536
1630
|
const nm = inj.sig.match(/function\s+(\w+)/)?.[1];
|
|
1537
1631
|
if (nm) sigCountByName.set(nm, (sigCountByName.get(nm) || 0) + 1);
|
|
1538
1632
|
}
|
|
1633
|
+
// A `{…}`/`[…]` destructuring parameter can't be reliably typed inline:
|
|
1634
|
+
// an EXTERNAL type (`({a}: {a: T})`) is attached to the closing-bracket
|
|
1635
|
+
// token and lost to the AST-based inline emitter, while an in-pattern
|
|
1636
|
+
// type reconstructs fine. Rather than split those cases, force the param
|
|
1637
|
+
// copy from the `.d.ts` signature (which carries the correct type for
|
|
1638
|
+
// both forms via dts.js) whenever the param list opens a pattern.
|
|
1639
|
+
function paramListHasPattern(line) {
|
|
1640
|
+
const open = line.indexOf('(');
|
|
1641
|
+
if (open < 0) return false;
|
|
1642
|
+
let depth = 0, atParamStart = false;
|
|
1643
|
+
for (let k = open; k < line.length; k++) {
|
|
1644
|
+
const c = line[k];
|
|
1645
|
+
if (c === '(') { depth++; if (depth === 1) atParamStart = true; continue; }
|
|
1646
|
+
if (c === ')') { if (--depth === 0) return false; continue; }
|
|
1647
|
+
if (depth !== 1) continue;
|
|
1648
|
+
if (c === ',') { atParamStart = true; continue; }
|
|
1649
|
+
if (c === ' ' || c === '\t') continue;
|
|
1650
|
+
if (atParamStart && (c === '{' || c === '[')) return true;
|
|
1651
|
+
atParamStart = false;
|
|
1652
|
+
}
|
|
1653
|
+
return false;
|
|
1654
|
+
}
|
|
1539
1655
|
function isSelfSufficient(inj) {
|
|
1540
1656
|
const nm = inj.sig.match(/function\s+(\w+)/)?.[1];
|
|
1541
1657
|
if (!nm || sigCountByName.get(nm) !== 1) return false;
|
|
1542
1658
|
if (extractTypeParams(inj.sig)) return false;
|
|
1543
1659
|
const impl = cl[inj.codeLine];
|
|
1660
|
+
if (paramListHasPattern(impl)) return false;
|
|
1661
|
+
// Arrow detection is authoritative and runs first: it's a precise
|
|
1662
|
+
// structural scan, so a fat-arrow line can't be misread by the
|
|
1663
|
+
// function-form brace heuristic below (which would otherwise pick a
|
|
1664
|
+
// nested object-literal `{` on an expression body). A fat-arrow
|
|
1665
|
+
// assignment (`NAME = (params) => …`) emits its params inline, so it
|
|
1666
|
+
// is self-sufficient when it carries an inline return type, OR when
|
|
1667
|
+
// the header has no return type to preserve. Fat-arrow return types
|
|
1668
|
+
// aren't expressible yet, so the header never carries one — moving it
|
|
1669
|
+
// (no copy) is complete and lossless. Once they are, an arrow that
|
|
1670
|
+
// lacks the inline return while the header has one is NOT self-
|
|
1671
|
+
// sufficient and falls through to the copy fallback (return preserved).
|
|
1672
|
+
const arrow = analyzeArrowImpl(impl, nm);
|
|
1673
|
+
if (arrow) {
|
|
1674
|
+
if (arrow.hasInlineReturn) return true;
|
|
1675
|
+
if (!hasExplicitReturn(inj.sig)) return true;
|
|
1676
|
+
return false;
|
|
1677
|
+
}
|
|
1678
|
+
// Function-form impl (`def`, thin-arrow `= function(`): self-sufficient
|
|
1679
|
+
// when an inline return type sits between the params `)` and the `{`.
|
|
1544
1680
|
const braceIdx = impl.lastIndexOf('{');
|
|
1545
|
-
if (braceIdx
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1681
|
+
if (braceIdx >= 0) {
|
|
1682
|
+
const head = impl.slice(0, braceIdx);
|
|
1683
|
+
const afterParen = head.slice(head.lastIndexOf(')') + 1).trim();
|
|
1684
|
+
if (afterParen.startsWith(':')) return true;
|
|
1685
|
+
}
|
|
1686
|
+
return false;
|
|
1549
1687
|
}
|
|
1550
1688
|
|
|
1551
1689
|
// First pass: copy typed params AND return types from signatures to
|
|
@@ -1609,7 +1747,11 @@ export function compileForCheck(filePath, source, compiler, opts = {}) {
|
|
|
1609
1747
|
// would force it to `any`. Functions with any untyped param would fire
|
|
1610
1748
|
// TS7006 twice (once on the injected sig, once on the impl) at the same
|
|
1611
1749
|
// source position — skip the injection so the user sees a single error.
|
|
1612
|
-
|
|
1750
|
+
// Destructuring-param functions get their full typed signature copied
|
|
1751
|
+
// onto the implementation (above); an extra overload signature is
|
|
1752
|
+
// redundant and, for a rename pattern (`{name: userName}`), its
|
|
1753
|
+
// body-less form makes `userName` an "unused renaming" (TS2842). Skip it.
|
|
1754
|
+
const overloads = injections.filter(inj => !isSelfSufficient(inj) && hasExplicitReturn(inj.sig) && !hasUntypedParam(inj.sig) && !paramListHasPattern(cl[inj.codeLine]));
|
|
1613
1755
|
|
|
1614
1756
|
// Adjust reverseMap: each overload injection shifts subsequent code lines down by 1.
|
|
1615
1757
|
// Compare against the original genLine (not genLine + offset) because bottom-up
|