flowfig 0.6.0 → 0.8.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/README.md +28 -8
- package/dist/browser.d.ts +1 -3
- package/dist/browser.js +67 -59
- package/dist/check.d.ts +5 -3
- package/dist/check.js +24 -17
- package/dist/cli.js +43 -49
- package/dist/code.d.ts +26 -0
- package/dist/code.js +568 -0
- package/dist/diff.js +0 -4
- package/dist/draw.d.ts +1 -3
- package/dist/draw.js +3 -7
- package/dist/edges.d.ts +7 -0
- package/dist/edges.js +510 -0
- package/dist/geometry.d.ts +6 -8
- package/dist/geometry.js +75 -82
- package/dist/gif.d.ts +1 -2
- package/dist/gif.js +115 -42
- package/dist/guide.d.ts +1 -4
- package/dist/guide.js +10 -11
- package/dist/imports.d.ts +11 -0
- package/dist/imports.js +235 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +20 -78
- package/dist/init.d.ts +3 -13
- package/dist/init.js +13 -18
- package/dist/load.d.ts +1 -3
- package/dist/load.js +2 -5
- package/dist/mcp.d.ts +1 -2
- package/dist/mcp.js +15 -15
- package/dist/model.d.ts +57 -109
- package/dist/model.js +20 -89
- package/dist/open.d.ts +2 -5
- package/dist/open.js +6 -11
- package/dist/page.d.ts +0 -1
- package/dist/page.js +1 -3
- package/dist/picker.d.ts +2 -4
- package/dist/picker.js +11 -14
- package/dist/png.js +9 -10
- package/dist/rail.d.ts +1 -3
- package/dist/rail.js +2 -16
- package/dist/scene.d.ts +8 -8
- package/dist/source.d.ts +1 -2
- package/dist/source.js +5 -5
- package/dist/svg.d.ts +5 -8
- package/dist/svg.js +50 -112
- package/dist/text.d.ts +0 -2
- package/dist/text.js +1 -4
- package/dist/type-index.d.ts +55 -0
- package/dist/type-index.js +383 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.js +733 -0
- package/dist/verify.d.ts +26 -4
- package/dist/verify.js +68 -26
- package/dist/version.d.ts +1 -2
- package/dist/version.js +2 -2
- package/package.json +4 -2
package/dist/types.js
ADDED
|
@@ -0,0 +1,733 @@
|
|
|
1
|
+
import { dirname } from 'node:path';
|
|
2
|
+
import { esc, langOf, locate, matchClose } from './code.js';
|
|
3
|
+
import { resolvePy } from './imports.js';
|
|
4
|
+
import { ancestors, bindings, declIn, defines, file, findType, global, goDirFiles, goIfaceMethods, goMethods, lastSeg, } from './type-index.js';
|
|
5
|
+
export { makeCtx, bindings, declIn, defines, ancestors, file, findType, rustMod, rustUses } from './type-index.js';
|
|
6
|
+
const SELF = new Set(['this', 'self', 'cls', 'Self', 'base', 'super']);
|
|
7
|
+
export const isUpper = (s) => /^[A-Z]/.test(s);
|
|
8
|
+
function openBracket(text, idx) {
|
|
9
|
+
const d = { ')': 0, ']': 0, '}': 0 };
|
|
10
|
+
for (let i = idx - 1; i >= 0; i--) {
|
|
11
|
+
const c = text[i];
|
|
12
|
+
if (c === ')' || c === ']' || c === '}')
|
|
13
|
+
d[c]++;
|
|
14
|
+
else if (c === '(' || c === '[' || c === '{') {
|
|
15
|
+
const close = { '(': ')', '[': ']', '{': '}' }[c];
|
|
16
|
+
if (d[close] > 0)
|
|
17
|
+
d[close]--;
|
|
18
|
+
else
|
|
19
|
+
return c;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
function openBack(text, close) {
|
|
25
|
+
let d = 0;
|
|
26
|
+
for (let i = close; i >= 0; i--) {
|
|
27
|
+
if (text[i] === ')')
|
|
28
|
+
d++;
|
|
29
|
+
else if (text[i] === '(' && --d === 0)
|
|
30
|
+
return i;
|
|
31
|
+
}
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
export function receiverChain(body, idx, lang) {
|
|
35
|
+
let pre = body.slice(Math.max(0, idx - 300), idx);
|
|
36
|
+
const sep = lang === 'rs' || lang === 'cs' || lang === 'java' ? /(\?\.|!\.|(?<!\.)\.|::)\s*$/ : /(\?\.|!\.|(?<!\.)\.)\s*$/;
|
|
37
|
+
let m = sep.exec(pre);
|
|
38
|
+
if (!m)
|
|
39
|
+
return null;
|
|
40
|
+
const chain = [];
|
|
41
|
+
for (;;) {
|
|
42
|
+
pre = pre.slice(0, m.index).replace(/\s+$/, '');
|
|
43
|
+
if (/[)\]>]$/.test(pre)) {
|
|
44
|
+
if (!pre.endsWith(')'))
|
|
45
|
+
return { chain, complex: true };
|
|
46
|
+
const before = pre.slice(0, openBack(pre, pre.length - 1)).replace(/\s+$/, '');
|
|
47
|
+
const nw = /\bnew\s+([\w$.]+)\s*(?:<[^<>]*>)?$/.exec(before);
|
|
48
|
+
if (nw && lang !== 'py' && lang !== 'go' && lang !== 'rs')
|
|
49
|
+
return { chain: ['#new', nw[1], ...chain], complex: false };
|
|
50
|
+
if (!chain.length && /\bsuper$/.test(before) && lang === 'py')
|
|
51
|
+
return { chain: ['super'], complex: false };
|
|
52
|
+
if (!/[\w$]$/.test(before))
|
|
53
|
+
return { chain, complex: true };
|
|
54
|
+
chain.unshift('()');
|
|
55
|
+
pre = before;
|
|
56
|
+
}
|
|
57
|
+
const id = /([A-Za-z_$][\w$]*)$/.exec(pre);
|
|
58
|
+
if (!id)
|
|
59
|
+
return { chain, complex: true };
|
|
60
|
+
chain.unshift(id[1]);
|
|
61
|
+
pre = pre.slice(0, id.index);
|
|
62
|
+
m = sep.exec(pre);
|
|
63
|
+
if (!m)
|
|
64
|
+
return { chain, complex: false };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const PRIM = new Set('string number boolean void int str float bool dict list set tuple bytes byte rune int64 int32 uint float64 String Integer Long Boolean long double char Vec HashMap'.split(' '));
|
|
68
|
+
const TOP = new Set(['any', 'unknown', 'object', 'Object', 'error', 'Any', 'dynamic']);
|
|
69
|
+
const EXTERNAL = { name: '#external' };
|
|
70
|
+
const TOP_TYPE = { name: '#top' };
|
|
71
|
+
export function normType(raw, lang) {
|
|
72
|
+
if (!raw)
|
|
73
|
+
return null;
|
|
74
|
+
let t = raw.trim().replace(/^(readonly|final|const)\s+/, '');
|
|
75
|
+
if (lang === 'py') {
|
|
76
|
+
t = t.replace(/^["']|["']$/g, '');
|
|
77
|
+
const opt = /^Optional\[(.+)\]$/.exec(t);
|
|
78
|
+
if (opt)
|
|
79
|
+
t = opt[1];
|
|
80
|
+
t =
|
|
81
|
+
t
|
|
82
|
+
.split('|')
|
|
83
|
+
.map((x) => x.trim())
|
|
84
|
+
.filter((x) => x && x !== 'None')[0] ?? '';
|
|
85
|
+
if (/^(List|Dict|Set|Tuple|Iterable|Sequence|list|dict|set|tuple)\[/.test(t))
|
|
86
|
+
return EXTERNAL;
|
|
87
|
+
t = t.replace(/\[.*$/, '');
|
|
88
|
+
}
|
|
89
|
+
if (lang === 'ts') {
|
|
90
|
+
t = t.replace(/^typeof\s+/, '');
|
|
91
|
+
const parts = t
|
|
92
|
+
.split('|')
|
|
93
|
+
.map((x) => x.trim())
|
|
94
|
+
.filter((x) => x && !/^(null|undefined|void)$/.test(x));
|
|
95
|
+
if (parts.length !== 1)
|
|
96
|
+
return null;
|
|
97
|
+
t = parts[0];
|
|
98
|
+
if (t.endsWith('[]'))
|
|
99
|
+
return EXTERNAL;
|
|
100
|
+
}
|
|
101
|
+
if (lang === 'go') {
|
|
102
|
+
t = t.replace(/^\*+/, '');
|
|
103
|
+
if (/^interface\s*\{/.test(t))
|
|
104
|
+
return TOP_TYPE;
|
|
105
|
+
if (/^(\[|map\[|chan\b|func\b)/.test(t))
|
|
106
|
+
return EXTERNAL;
|
|
107
|
+
}
|
|
108
|
+
if (lang === 'rs') {
|
|
109
|
+
for (;;) {
|
|
110
|
+
const n = t.replace(/^&\s*('\w+\s+)?/, '').replace(/^(mut|dyn|impl)\s+/, '');
|
|
111
|
+
const w = /^(Box|Arc|Rc|RefCell|Mutex)<(.+)>$/.exec(n);
|
|
112
|
+
const next = w ? w[2] : n;
|
|
113
|
+
if (next === t)
|
|
114
|
+
break;
|
|
115
|
+
t = next;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (lang === 'cs')
|
|
119
|
+
t = t.replace(/\?$/, '');
|
|
120
|
+
t = t.replace(/<.*$/, '').trim();
|
|
121
|
+
if (lang === 'java' || lang === 'cs')
|
|
122
|
+
t = t.replace(/\[\]$/, '');
|
|
123
|
+
const m = /^([\w$]+(?:(?:\.|::)[\w$]+)*)$/.exec(t);
|
|
124
|
+
if (!m)
|
|
125
|
+
return null;
|
|
126
|
+
const segs = m[1].split(/\.|::/);
|
|
127
|
+
const name = segs.at(-1);
|
|
128
|
+
if (TOP.has(name) && (segs.length === 1 || name === 'Any'))
|
|
129
|
+
return TOP_TYPE;
|
|
130
|
+
if (PRIM.has(name) && segs.length === 1)
|
|
131
|
+
return EXTERNAL;
|
|
132
|
+
return { name, qual: segs.length > 1 ? segs.slice(0, -1).join('.') : null };
|
|
133
|
+
}
|
|
134
|
+
const TYPE_RE = String.raw `[\w$.:]+(?:<[^;{}()=]*?>)?(?:\[\])?\??`;
|
|
135
|
+
function headOf(text, lang) {
|
|
136
|
+
if (lang === 'py') {
|
|
137
|
+
const m = /:[ \t]*(#[^\n]*)?\n/.exec(text);
|
|
138
|
+
return m ? text.slice(0, m.index) : text;
|
|
139
|
+
}
|
|
140
|
+
const i = text.search(/\{|=>/);
|
|
141
|
+
return i < 0 ? text : text.slice(0, i);
|
|
142
|
+
}
|
|
143
|
+
const CALL = {
|
|
144
|
+
ts: (N) => new RegExp(`\\b(?:const|let|var)\\s+${N}\\s*=\\s*(?:await\\s+)?((?:this|[\\w$]+)(?:\\??\\.[\\w$]+)*)\\s*\\(`),
|
|
145
|
+
py: (N) => new RegExp(`(?:^|\\n)[ \\t]*${N}\\s*=\\s*(?:await\\s+)?((?:self|\\w+)(?:\\.\\w+)*)\\s*\\(`),
|
|
146
|
+
go: (N) => new RegExp(`(?:^|\\n)[ \\t]*${N}(?:\\s*,\\s*\\w+)*\\s*:?=\\s*(\\w+(?:\\.\\w+)*)\\s*\\(`),
|
|
147
|
+
java: (N) => new RegExp(`\\bvar\\s+${N}\\s*=\\s*((?:this|\\w+)(?:\\.\\w+)*)\\s*\\(`),
|
|
148
|
+
cs: (N) => new RegExp(`\\bvar\\s+${N}\\s*=\\s*(?:await\\s+)?((?:this|\\w+)(?:\\??\\.\\w+)*)\\s*\\(`),
|
|
149
|
+
rs: (N) => new RegExp(`\\blet\\s+(?:mut\\s+)?${N}\\s*=\\s*((?:self|\\w+)(?:(?:\\.|::)\\w+)*)\\s*\\(`),
|
|
150
|
+
};
|
|
151
|
+
function callLocal(text, n, lang) {
|
|
152
|
+
const N = esc(n);
|
|
153
|
+
if ((text.match(new RegExp(`(?<![\\w$.])${N}\\s*(?:,\\s*\\w+\\s*)*:?=(?!=)`, 'g')) ?? []).length !== 1)
|
|
154
|
+
return null;
|
|
155
|
+
const m = CALL[lang](N).exec(text);
|
|
156
|
+
if (!m)
|
|
157
|
+
return null;
|
|
158
|
+
const open = m.index + m[0].length - 1;
|
|
159
|
+
const after = text.slice(matchClose(text, open) + 1);
|
|
160
|
+
if (!(lang === 'rs' ? /^\s*(\?|\.await)*\s*;/ : /^\s*!?\s*(;|\n|$)/).test(after))
|
|
161
|
+
return null;
|
|
162
|
+
return { chain: [...m[1].split(/\??\.|::/), '()'] };
|
|
163
|
+
}
|
|
164
|
+
export function localType(cx, info, text, n, lang, hasHead = true) {
|
|
165
|
+
const r = localType0(cx, info, text, n, lang, hasHead);
|
|
166
|
+
return r?.declared ? (callLocal(text, n, lang) ?? r) : r;
|
|
167
|
+
}
|
|
168
|
+
function localType0(cx, info, text, n, lang, hasHead) {
|
|
169
|
+
const head = hasHead ? headOf(text, lang) : '';
|
|
170
|
+
const N = esc(n);
|
|
171
|
+
const NB = `(?<![\\w$.])${N}`;
|
|
172
|
+
const tries = [];
|
|
173
|
+
const inHead = (re) => re.test(head);
|
|
174
|
+
const isLocal = (x) => new RegExp(`\\b(const|let|var)\\s+([[{][^=]*)?${esc(x)}\\b|(?<![\\w$.])${esc(x)}\\s*[,)=:]`).test(text);
|
|
175
|
+
const chainRe = {
|
|
176
|
+
ts: `\\b(?:const|let|var)\\s+${N}\\s*=\\s*(?:await\\s+)?((?:this|[\\w$]+)(?:\\??\\.[\\w$]+)+)\\s*;`,
|
|
177
|
+
py: `(?:^|\\n)[ \\t]*${N}\\s*=\\s*((?:self|\\w+)(?:\\.\\w+)+)[ \\t]*(?=\\n)`,
|
|
178
|
+
go: `${NB}\\s*:?=\\s*(\\w+(?:\\.\\w+)+)[ \\t]*(?=\\n)`,
|
|
179
|
+
rs: `\\blet\\s+(?:mut\\s+)?${N}\\s*=\\s*&?\\s*(\\w+(?:\\.\\w+)+)\\s*;`,
|
|
180
|
+
java: `\\bvar\\s+${N}\\s*=\\s*((?:this|\\w+)(?:\\.\\w+)+)\\s*;`,
|
|
181
|
+
cs: `\\bvar\\s+${N}\\s*=\\s*((?:this|\\w+)(?:\\??\\.\\w+)+)\\s*;`,
|
|
182
|
+
}[lang];
|
|
183
|
+
const cm = new RegExp(chainRe).exec(text);
|
|
184
|
+
if (cm)
|
|
185
|
+
return { chain: cm[1].split(/\??\./) };
|
|
186
|
+
if (lang === 'ts') {
|
|
187
|
+
const cast = new RegExp(`\\b(?:const|let|var)\\s+${N}\\s*=[^;\\n]*?\\bas\\s+([\\w$.]+)\\s*;?[ \\t]*(?=\\n)`).exec(text);
|
|
188
|
+
if (cast)
|
|
189
|
+
tries.push({ type: cast[1] });
|
|
190
|
+
for (const m of text.matchAll(new RegExp(`${NB}\\s*\\??\\s*:\\s*(${TYPE_RE}(?:\\s*\\|\\s*${TYPE_RE})*)`, 'g'))) {
|
|
191
|
+
const pre = text.slice(0, m.index);
|
|
192
|
+
if (/\b(const|let|var)\s*$/.test(pre) ||
|
|
193
|
+
(openBracket(text, m.index) === '(' && /[(,]\s*((public|private|protected|readonly)\s+)*$/.test(pre)))
|
|
194
|
+
tries.push({ type: m[1] });
|
|
195
|
+
}
|
|
196
|
+
let m = new RegExp(`\\b(?:const|let|var)\\s+${N}\\s*=\\s*(?:await\\s+)?new\\s+([\\w$.]+)`).exec(text);
|
|
197
|
+
if (m && !isLocal(m[1]))
|
|
198
|
+
tries.push({ type: m[1] });
|
|
199
|
+
else if (m && !tries.length)
|
|
200
|
+
return { declared: true };
|
|
201
|
+
m = new RegExp(`\\b(?:const|let|var)\\s+${N}\\s*=\\s*(?:await\\s+)?([A-Z][\\w$]*)\\.[\\w$]+\\s*[(<]`).exec(text);
|
|
202
|
+
if (m && (bindings(cx, info).has(m[1]) || declIn(info, m[1])))
|
|
203
|
+
tries.push({ type: m[1] });
|
|
204
|
+
if (!tries.length &&
|
|
205
|
+
(new RegExp(`\\b(const|let|var)\\s+(${N}\\b|[[{][^=]*${NB}\\b)|\\bfor\\s*\\(\\s*(const|let|var)\\s+${N}\\b|\\bcatch\\s*\\(\\s*${N}\\b`).test(text) ||
|
|
206
|
+
inHead(new RegExp(`${NB}\\s*[,)=?]`))))
|
|
207
|
+
return { declared: true };
|
|
208
|
+
}
|
|
209
|
+
else if (lang === 'py') {
|
|
210
|
+
for (const m of text.matchAll(new RegExp(`${NB}\\s*:\\s*([\\w.\\[\\]"' |]+?)\\s*[,)=\\n]`, 'g'))) {
|
|
211
|
+
const pre = text.slice(0, m.index);
|
|
212
|
+
if (/\blambda\b[^:]*$/.test(pre))
|
|
213
|
+
continue;
|
|
214
|
+
if (m.index < head.length || /(^|\n)[ \t]*$/.test(pre))
|
|
215
|
+
tries.push({ type: m[1] });
|
|
216
|
+
}
|
|
217
|
+
let m = new RegExp(`(^|\\n)[ \\t]*${N}\\s*=\\s*((?:[\\w]+\\.)*[A-Z]\\w*)\\s*\\(`).exec(text);
|
|
218
|
+
if (m)
|
|
219
|
+
tries.push({ type: m[2] });
|
|
220
|
+
m = new RegExp(`(^|\\n)[ \\t]*${N}\\s*=\\s*((?:[a-z_]\\w*\\.)*[A-Z]\\w*)\\.\\w+(\\.\\w+)*\\s*\\(`).exec(text);
|
|
221
|
+
if (m)
|
|
222
|
+
tries.push({ type: m[2] });
|
|
223
|
+
if (!tries.length &&
|
|
224
|
+
(new RegExp(`(^|\\n)[ \\t]*${N}\\s*(,[^=\\n]*)?=[^=]|\\bfor\\s+([\\w, ]*,\\s*)?${N}\\b|\\bas\\s+${N}\\b`).test(text) ||
|
|
225
|
+
inHead(new RegExp(`${NB}\\s*[,)=]`))))
|
|
226
|
+
return { declared: true };
|
|
227
|
+
}
|
|
228
|
+
else if (lang === 'go') {
|
|
229
|
+
for (const m of head.matchAll(new RegExp(`${NB}(?:\\s*,\\s*\\w+)*\\s+(\\*?[\\w.]+)`, 'g')))
|
|
230
|
+
if (openBracket(head, m.index) === '(')
|
|
231
|
+
tries.push({ type: m[1] });
|
|
232
|
+
let m = new RegExp(`\\bvar\\s+${N}\\s+(\\*?[\\w.]+)`).exec(text);
|
|
233
|
+
if (m)
|
|
234
|
+
tries.push({ type: m[1] });
|
|
235
|
+
m = new RegExp(`${NB}(?:\\s*,\\s*\\w+)*\\s*:?=\\s*&?([\\w.]+)\\s*\\{`).exec(text);
|
|
236
|
+
if (m)
|
|
237
|
+
tries.push({ type: m[1] });
|
|
238
|
+
m = new RegExp(`${NB}(?:\\s*,\\s*\\w+)*\\s*:=\\s*((?:\\w+\\.)?New\\w*)\\s*\\(`).exec(text);
|
|
239
|
+
if (m)
|
|
240
|
+
tries.push({ ctor: m[1] });
|
|
241
|
+
if (!tries.length && new RegExp(`${NB}(\\s*,\\s*\\w+)*\\s*:=|\\b\\w+\\s*,\\s*${N}\\s*(,\\s*\\w+\\s*)*:=|\\bvar\\s+${N}\\b`).test(text))
|
|
242
|
+
return { declared: true };
|
|
243
|
+
}
|
|
244
|
+
else if (lang === 'java' || lang === 'cs') {
|
|
245
|
+
for (const m of text.matchAll(new RegExp(`(?<![\\w.])([A-Z][\\w.]*(?:<[^;(){}=]*?>)?(?:\\[\\])?\\??)\\s+${N}\\s*[=;,):]`, 'g')))
|
|
246
|
+
if (!/\b(new|return|throw)\s*$/.test(text.slice(0, m.index)))
|
|
247
|
+
tries.push({ type: m[1] });
|
|
248
|
+
const m = new RegExp(`\\bvar\\s+${N}\\s*=\\s*new\\s+([\\w.]+)`).exec(text);
|
|
249
|
+
if (m)
|
|
250
|
+
tries.push({ type: m[1] });
|
|
251
|
+
if (!tries.length &&
|
|
252
|
+
new RegExp(`\\bvar\\s+${N}\\b|\\b(out\\s+var|foreach\\s*\\(\\s*var)\\s+${N}\\b|${NB}\\s*=>|\\(\\s*${N}\\s*,|,\\s*${N}\\s*\\)\\s*->|${NB}\\s*->`).test(text))
|
|
253
|
+
return { declared: true };
|
|
254
|
+
}
|
|
255
|
+
else if (lang === 'rs') {
|
|
256
|
+
for (const m of text.matchAll(new RegExp(`${NB}\\s*:\\s*((?:&\\s*(?:'\\w+\\s+)?)?(?:mut\\s+)?(?:dyn\\s+|impl\\s+)?[\\w:]+(?:<[^;{}=]*?>)?)`, 'g'))) {
|
|
257
|
+
const pre = text.slice(0, m.index);
|
|
258
|
+
if (openBracket(text, m.index) === '(' || /\blet\s+(mut\s+)?$/.test(pre))
|
|
259
|
+
tries.push({ type: m[1] });
|
|
260
|
+
}
|
|
261
|
+
const m = new RegExp(`\\b(?:let\\s+(?:mut\\s+)?|let\\s+Some\\(\\s*(?:mut\\s+)?|for\\s+(?:mut\\s+)?|Some\\(\\s*)${N}\\s*\\)?\\s*(?:=|\\bin\\b)\\s*(?:&\\s*)?([A-Z]\\w*)\\s*(::|\\{)`).exec(text);
|
|
262
|
+
if (m)
|
|
263
|
+
tries.push({ type: m[1] });
|
|
264
|
+
if (!tries.length &&
|
|
265
|
+
new RegExp(`\\blet\\s+(mut\\s+)?${N}\\b|\\bfor\\s+${N}\\b|Some\\(\\s*${N}\\s*\\)|\\|[^|]*${NB}\\b[^|]*\\|`).test(text))
|
|
266
|
+
return { declared: true };
|
|
267
|
+
for (const t of tries) {
|
|
268
|
+
const g = /^(?:&\s*)?(?:mut\s+)?([A-Z])$/.exec(t.type.trim());
|
|
269
|
+
const bound = g && new RegExp(`[<,]\\s*${g[1]}\\s*:\\s*([\\w:]+)`).exec(head);
|
|
270
|
+
if (bound)
|
|
271
|
+
t.type = bound[1];
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (lang === 'ts' || lang === 'java')
|
|
275
|
+
for (const t of tries) {
|
|
276
|
+
const bound = t.type && new RegExp(`[<,]\\s*${esc(t.type.trim())}\\s+extends\\s+([\\w$.]+)`).exec(head);
|
|
277
|
+
if (bound)
|
|
278
|
+
t.type = bound[1];
|
|
279
|
+
}
|
|
280
|
+
if (new Set(tries.map((t) => t.type ?? t.ctor)).size > 1)
|
|
281
|
+
return { declared: true };
|
|
282
|
+
return tries[0] ?? null;
|
|
283
|
+
}
|
|
284
|
+
function fieldType(cx, d, f, depth = 0) {
|
|
285
|
+
if (!d)
|
|
286
|
+
return null;
|
|
287
|
+
const { info } = d;
|
|
288
|
+
const lang = info.lang;
|
|
289
|
+
const body = info.code.slice(d.start, d.end);
|
|
290
|
+
const src = info.keep.slice(d.start, d.end);
|
|
291
|
+
const F = esc(f);
|
|
292
|
+
let m;
|
|
293
|
+
if (lang === 'ts') {
|
|
294
|
+
m = new RegExp(`(^|[\\n;{])[ \\t]*(@[\\w.]+(\\([^)]*\\))?\\s*)*((public|private|protected|static|readonly|declare|override)\\s+)*${F}\\s*[?!]?\\s*:\\s*(${TYPE_RE}(?:\\s*\\|\\s*${TYPE_RE})*)`).exec(body);
|
|
295
|
+
if (m)
|
|
296
|
+
return { type: m[6] };
|
|
297
|
+
m = new RegExp(`\\b(public|private|protected|readonly)\\s+(readonly\\s+)?${F}\\s*\\??\\s*:\\s*(${TYPE_RE})`).exec(body);
|
|
298
|
+
if (m)
|
|
299
|
+
return { type: m[3] };
|
|
300
|
+
m = new RegExp(`\\bthis\\.${F}\\s*=\\s*new\\s+([\\w$.]+)`).exec(body);
|
|
301
|
+
if (m)
|
|
302
|
+
return { type: m[1] };
|
|
303
|
+
}
|
|
304
|
+
else if (lang === 'py') {
|
|
305
|
+
m = new RegExp(`\\bself\\.${F}\\s*(?::\\s*([\\w.\\[\\]"' |]+?)\\s*)?=\\s*(?:[a-z_]\\w*\\.)*([A-Z]\\w*)\\s*\\(`).exec(src);
|
|
306
|
+
if (m)
|
|
307
|
+
return { type: m[1] ?? m[2] };
|
|
308
|
+
m = new RegExp(`\\n[ \\t]+(?:self\\.)?${F}\\s*:\\s*([\\w.\\[\\]"' |]+?)\\s*[=\\n]`).exec(src);
|
|
309
|
+
if (m)
|
|
310
|
+
return { type: m[1] };
|
|
311
|
+
}
|
|
312
|
+
else if (lang === 'go') {
|
|
313
|
+
m = new RegExp(`\\n[ \\t]*${F}(?:\\s*,\\s*\\w+)*[ \\t]+(\\*?[\\w.\\[\\]]+)`).exec(body);
|
|
314
|
+
if (m)
|
|
315
|
+
return { type: m[1] };
|
|
316
|
+
m = new RegExp(`\\n[ \\t]*\\*?((?:\\w+\\.)?${F})[ \\t]*(?=\\n)`).exec(body);
|
|
317
|
+
if (m)
|
|
318
|
+
return { type: m[1] };
|
|
319
|
+
}
|
|
320
|
+
else if (lang === 'java' || lang === 'cs') {
|
|
321
|
+
m = new RegExp(`(?<![\\w.])([A-Z][\\w.]*(?:<[^;(){}=]*?>)?(?:\\[\\])?\\??)\\s+${F}\\s*(?:[;=]|\\{\\s*get)`).exec(body);
|
|
322
|
+
if (m)
|
|
323
|
+
return { type: m[1] };
|
|
324
|
+
if (lang === 'cs') {
|
|
325
|
+
const h = new RegExp(`\\b${esc(d.name)}\\s*(?:<[^>]*>)?\\s*\\(([^)]*)\\)`).exec(body.slice(0, body.indexOf('{') + 1 || 400));
|
|
326
|
+
const p = h && new RegExp(`([A-Z][\\w.]*(?:<[^,()]*?>)?\\??)\\s+${F}\\s*(,|$)`).exec(h[1]);
|
|
327
|
+
if (p)
|
|
328
|
+
return { type: p[1] };
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
else if (lang === 'rs') {
|
|
332
|
+
m = new RegExp(`(?<![\\w])(?:pub(?:\\([^)]*\\))?\\s+)?${F}\\s*:\\s*([^,\\n}]+)`).exec(body);
|
|
333
|
+
if (m)
|
|
334
|
+
return { type: m[1].trim() };
|
|
335
|
+
}
|
|
336
|
+
if (depth > 0)
|
|
337
|
+
return null;
|
|
338
|
+
for (const a of ancestors(cx, d).slice(0, 4)) {
|
|
339
|
+
const r = fieldType(cx, a.d, f, 1);
|
|
340
|
+
if (r && a.d)
|
|
341
|
+
return { ...r, ctx: a.d.info };
|
|
342
|
+
}
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
function goCtorType(cx, info, ctor) {
|
|
346
|
+
const [q, fn] = ctor.includes('.') ? ctor.split('.') : [null, ctor];
|
|
347
|
+
let dir = dirname(info.path);
|
|
348
|
+
if (q) {
|
|
349
|
+
const b = bindings(cx, info).get(q);
|
|
350
|
+
if (!b?.mod)
|
|
351
|
+
return null;
|
|
352
|
+
dir = b.mod;
|
|
353
|
+
}
|
|
354
|
+
for (const f of goDirFiles(cx, dir)) {
|
|
355
|
+
const m = new RegExp(`\\bfunc\\s+${esc(fn)}\\s*(\\[[^\\]]*\\])?\\s*\\(`).exec(f.code);
|
|
356
|
+
if (!m)
|
|
357
|
+
continue;
|
|
358
|
+
const close = matchClose(f.code, m.index + m[0].length - 1);
|
|
359
|
+
const r = /^\s*\(?\s*\*?([\w.]+)/.exec(f.code.slice(close + 1));
|
|
360
|
+
return r ? { type: r[1], ctx: f } : null;
|
|
361
|
+
}
|
|
362
|
+
return null;
|
|
363
|
+
}
|
|
364
|
+
function topLevel(fi) {
|
|
365
|
+
const c = fi.code;
|
|
366
|
+
if (fi.lang === 'py')
|
|
367
|
+
return c
|
|
368
|
+
.split('\n')
|
|
369
|
+
.filter((l) => /^\S/.test(l))
|
|
370
|
+
.join('\n');
|
|
371
|
+
let d = 0;
|
|
372
|
+
let out = '';
|
|
373
|
+
for (const ch of c) {
|
|
374
|
+
if (ch === '{')
|
|
375
|
+
d++;
|
|
376
|
+
if (d === 0)
|
|
377
|
+
out += ch;
|
|
378
|
+
if (ch === '}')
|
|
379
|
+
d--;
|
|
380
|
+
}
|
|
381
|
+
return out;
|
|
382
|
+
}
|
|
383
|
+
function rustUnwrap(cx, raw, depth, at) {
|
|
384
|
+
const strip = (s) => s
|
|
385
|
+
.trim()
|
|
386
|
+
.replace(/^&\s*('\w+\s+)?/, '')
|
|
387
|
+
.replace(/^(mut|dyn|impl)\s+/, '');
|
|
388
|
+
let t = strip(strip(raw));
|
|
389
|
+
if (depth > 4)
|
|
390
|
+
return [t, at];
|
|
391
|
+
const g = global(cx, 'rs');
|
|
392
|
+
const w = /^([\w:]+)<(.+)>$/.exec(t);
|
|
393
|
+
if (w) {
|
|
394
|
+
const W = lastSeg(w[1]);
|
|
395
|
+
let d = 0;
|
|
396
|
+
let arg = '';
|
|
397
|
+
for (const ch of w[2]) {
|
|
398
|
+
if (ch === '<')
|
|
399
|
+
d++;
|
|
400
|
+
if (ch === '>')
|
|
401
|
+
d--;
|
|
402
|
+
if (ch === ',' && d === 0)
|
|
403
|
+
break;
|
|
404
|
+
arg += ch;
|
|
405
|
+
}
|
|
406
|
+
arg = arg.trim();
|
|
407
|
+
if (/^'\w+$/.test(arg))
|
|
408
|
+
return rustUnwrap(cx, W, depth + 1, at);
|
|
409
|
+
if (!g.types.has(W) && !g.impls.has(W) && !g.aliases.has(W))
|
|
410
|
+
return rustUnwrap(cx, arg, depth + 1, at);
|
|
411
|
+
t = W;
|
|
412
|
+
}
|
|
413
|
+
const alias = g.aliases.get(lastSeg(t.replace(/<.*$/, '')));
|
|
414
|
+
return alias ? rustUnwrap(cx, alias.to, depth + 1, alias.path) : [t, at];
|
|
415
|
+
}
|
|
416
|
+
function typeFrom(cx, raw, lang, ctx) {
|
|
417
|
+
const [t, at] = lang === 'rs' ? rustUnwrap(cx, raw, 0) : [raw];
|
|
418
|
+
const n = normType(t, lang);
|
|
419
|
+
return n ? { kind: 'type', name: n.name, qual: n.qual, ctx: (at && file(cx, at)) || ctx } : { kind: 'unknown' };
|
|
420
|
+
}
|
|
421
|
+
const NO_RT = { kind: 'unknown' };
|
|
422
|
+
const MODIFIER = /^(public|private|protected|static|internal|async|override|virtual|abstract|new|final|sealed|extern)$/;
|
|
423
|
+
function returnOf(cx, f, at, method) {
|
|
424
|
+
const { code, lang } = f;
|
|
425
|
+
let s = at.start;
|
|
426
|
+
for (let nl = code.indexOf('\n', s); /^[ \t]*(@|\[|#\[)/.test(code.slice(s, s + 3)) && nl > 0 && nl < at.end; nl = code.indexOf('\n', s))
|
|
427
|
+
s = nl + 1;
|
|
428
|
+
if (lang === 'go' && !method && !/^func\s+\w/.test(code.slice(s, s + 40)))
|
|
429
|
+
return NO_RT;
|
|
430
|
+
let i = code.indexOf('(', s);
|
|
431
|
+
if (lang === 'go' && /^func\s*\(/.test(code.slice(s, s + 20)))
|
|
432
|
+
i = code.indexOf('(', matchClose(code, i) + 1);
|
|
433
|
+
if (i < 0 || i >= at.end || /[{;]|\b(class|interface|struct|enum|trait)\b/.test(code.slice(s, i)))
|
|
434
|
+
return NO_RT;
|
|
435
|
+
const tail = code.slice(matchClose(code, i) + 1, at.end);
|
|
436
|
+
let raw;
|
|
437
|
+
if (lang === 'ts')
|
|
438
|
+
raw = /^\s*:\s*([^{=;]+?)\s*(?:\{|=>|;|$)/.exec(tail)?.[1];
|
|
439
|
+
else if (lang === 'py')
|
|
440
|
+
raw = /^\s*->\s*([^:\n]+?)\s*:/.exec(tail)?.[1];
|
|
441
|
+
else if (lang === 'rs')
|
|
442
|
+
raw = /^\s*->\s*([^{;]+?)\s*(?:where\b[^{;]*)?(?:\{|;|$)/.exec(tail)?.[1];
|
|
443
|
+
else if (lang === 'go') {
|
|
444
|
+
const open = /^[ \t]*\(/.exec(tail);
|
|
445
|
+
if (open) {
|
|
446
|
+
const parts = tail.slice(open[0].length, matchClose(tail, open[0].length - 1)).split(',');
|
|
447
|
+
const word = '(?!(?:chan|func|map|interface|struct)\\b)\\w+';
|
|
448
|
+
const named = parts.some((p) => new RegExp(`^\\s*${word}\\s+\\S`).test(p));
|
|
449
|
+
raw = named ? new RegExp(`^\\s*${word}\\s+(\\*?[\\w.]+)\\s*$`).exec(parts[0])?.[1] : parts[0].trim();
|
|
450
|
+
}
|
|
451
|
+
else
|
|
452
|
+
raw = /^[ \t]*(\*?[\w.]+)[ \t]*(?:\{|\n|$)/.exec(tail)?.[1];
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
raw = /([A-Za-z_][\w.]*(?:<[^()]*>)?(?:\[\])?\??)\s+[\w$]+\s*(?:<[^()]*>)?\s*$/.exec(code.slice(s, i))?.[1];
|
|
456
|
+
if (raw && MODIFIER.test(raw))
|
|
457
|
+
raw = undefined;
|
|
458
|
+
}
|
|
459
|
+
if (!raw || /\b(any|unknown|object|Object)\b|interface\s*\{/.test(raw))
|
|
460
|
+
return NO_RT;
|
|
461
|
+
const wrap = {
|
|
462
|
+
ts: /^(?:Promise|PromiseLike|Awaited)<([\s\S]*)>$/,
|
|
463
|
+
java: /^(?:Optional|CompletableFuture)<([\s\S]*)>$/,
|
|
464
|
+
cs: /^(?:Task|ValueTask)<([\s\S]*)>$/,
|
|
465
|
+
}[lang];
|
|
466
|
+
const rt = typeFrom(cx, wrap?.exec(raw)?.[1] ?? raw, lang, f);
|
|
467
|
+
return rt.kind === 'type' && (rt.name === '#external' || findType(cx, f, rt.name, rt.qual)) ? rt : NO_RT;
|
|
468
|
+
}
|
|
469
|
+
function fnReturn(cx, files, name) {
|
|
470
|
+
for (const f of files) {
|
|
471
|
+
const at = locate(f, name);
|
|
472
|
+
if (at)
|
|
473
|
+
return returnOf(cx, f, at, false);
|
|
474
|
+
}
|
|
475
|
+
return NO_RT;
|
|
476
|
+
}
|
|
477
|
+
function methodReturn(cx, d, name) {
|
|
478
|
+
const N = esc(name);
|
|
479
|
+
const dup = new RegExp(`(?:^|[\\n;{}>\\]?])[ \\t]*(?:[\\w.<>\\[\\]?,]+[ \\t]+)*${N}[ \\t]*[<(]`, 'g');
|
|
480
|
+
for (const x of [d, ...ancestors(cx, d).map((a) => a.d)]) {
|
|
481
|
+
if (!x || !defines(cx, x, name))
|
|
482
|
+
continue;
|
|
483
|
+
if ((x.info.code.slice(x.start, x.end).match(dup) ?? []).length > 1)
|
|
484
|
+
return NO_RT;
|
|
485
|
+
for (const f of x.info.lang === 'go' ? goDirFiles(cx, dirname(x.info.path)) : [x.info]) {
|
|
486
|
+
const at = locate(f, `${x.name}.${name}`);
|
|
487
|
+
if (at)
|
|
488
|
+
return returnOf(cx, f, at, true);
|
|
489
|
+
}
|
|
490
|
+
return NO_RT;
|
|
491
|
+
}
|
|
492
|
+
return NO_RT;
|
|
493
|
+
}
|
|
494
|
+
function importedValueType(cx, b) {
|
|
495
|
+
const mi = file(cx, b.mod);
|
|
496
|
+
if (!mi)
|
|
497
|
+
return null;
|
|
498
|
+
let name = b.imported;
|
|
499
|
+
if (name === 'default')
|
|
500
|
+
name = /\bexport\s+default\s+([A-Za-z_$][\w$]*)\s*;?\s*(\n|$)/.exec(mi.code)?.[1];
|
|
501
|
+
if (!name || declIn(mi, name))
|
|
502
|
+
return null;
|
|
503
|
+
const lt = localType(cx, mi, topLevel(mi), name, mi.lang);
|
|
504
|
+
return lt?.type ? typeFrom(cx, lt.type, mi.lang, mi) : null;
|
|
505
|
+
}
|
|
506
|
+
export function receiver(cx, fi, body, chain, fromContainer, fromDecl, depth = 0) {
|
|
507
|
+
const lang = fi.lang;
|
|
508
|
+
const unknown = { kind: 'unknown' };
|
|
509
|
+
const head = chain[0];
|
|
510
|
+
const imps = bindings(cx, fi);
|
|
511
|
+
let cur = null;
|
|
512
|
+
const goRecv = lang === 'go' ? /^\s*func\s*\(\s*(\w+)\s+\*?\w+/.exec(body)?.[1] : null;
|
|
513
|
+
if (head === '#new') {
|
|
514
|
+
cur = typeFrom(cx, chain[1], lang, fi);
|
|
515
|
+
chain = chain.slice(1);
|
|
516
|
+
}
|
|
517
|
+
else if (SELF.has(head) || head === goRecv) {
|
|
518
|
+
if (!fromContainer)
|
|
519
|
+
return unknown;
|
|
520
|
+
if (head === 'super' || head === 'base') {
|
|
521
|
+
if (chain.length > 1)
|
|
522
|
+
return unknown;
|
|
523
|
+
return fromDecl ? { kind: 'super' } : unknown;
|
|
524
|
+
}
|
|
525
|
+
cur = { kind: 'type', name: fromContainer, ctx: fi, decl: fromDecl };
|
|
526
|
+
}
|
|
527
|
+
else if (chain[1] === '()') {
|
|
528
|
+
const b = imps.get(head);
|
|
529
|
+
const mi = b?.mod && !b.dir && b.imported !== '*' && b.imported !== 'default' ? file(cx, b.mod) : null;
|
|
530
|
+
if (lang === 'py' && isUpper(b?.imported ?? head))
|
|
531
|
+
cur = { kind: 'type', name: b?.imported ?? head, ctx: fi };
|
|
532
|
+
else if (b)
|
|
533
|
+
cur = mi ? fnReturn(cx, [mi], b.imported) : unknown;
|
|
534
|
+
else
|
|
535
|
+
cur = fnReturn(cx, lang === 'go' ? goDirFiles(cx, dirname(fi.path)) : [fi], head);
|
|
536
|
+
}
|
|
537
|
+
else {
|
|
538
|
+
const lt = localType(cx, fi, body, head, lang);
|
|
539
|
+
if (lt?.chain && depth < 3 && lt.chain[0] !== head)
|
|
540
|
+
cur = receiver(cx, fi, body, lt.chain, fromContainer, fromDecl, depth + 1);
|
|
541
|
+
else if (lt?.chain)
|
|
542
|
+
return unknown;
|
|
543
|
+
else if (lt?.type)
|
|
544
|
+
cur = typeFrom(cx, lt.type, lang, fi);
|
|
545
|
+
else if (lt?.ctor) {
|
|
546
|
+
const c = goCtorType(cx, fi, lt.ctor);
|
|
547
|
+
cur = c ? typeFrom(cx, c.type, lang, c.ctx) : unknown;
|
|
548
|
+
}
|
|
549
|
+
else if (lt?.declared && !imps.has(head))
|
|
550
|
+
return unknown;
|
|
551
|
+
else {
|
|
552
|
+
const b = imps.get(head);
|
|
553
|
+
if (lang === 'rs' && (/^[a-z_]/.test(head) || head === 'crate'))
|
|
554
|
+
return chain.includes('()') ? unknown : { kind: 'rsmod' };
|
|
555
|
+
const iv = b?.mod && !b.dir && b.imported !== '*' ? importedValueType(cx, b) : null;
|
|
556
|
+
if (iv)
|
|
557
|
+
cur = iv;
|
|
558
|
+
else if (b) {
|
|
559
|
+
if (b.dir || b.imported === '*')
|
|
560
|
+
cur = { kind: 'module', mod: b.mod, dir: b.dir };
|
|
561
|
+
else if (b.imported === 'default' && !isUpper(head))
|
|
562
|
+
cur = { kind: 'value', name: head, mod: b.mod };
|
|
563
|
+
else if (isUpper(b.imported === 'default' ? head : b.imported))
|
|
564
|
+
cur = { kind: 'type', name: b.imported === 'default' ? head : b.imported, ctx: fi };
|
|
565
|
+
else
|
|
566
|
+
cur = { kind: 'value', name: b.imported, mod: b.mod };
|
|
567
|
+
}
|
|
568
|
+
else if ((lang === 'java' || lang === 'cs') && fromDecl && fieldType(cx, fromDecl, head)) {
|
|
569
|
+
const ft = fieldType(cx, fromDecl, head);
|
|
570
|
+
cur = typeFrom(cx, ft.type, lang, ft.ctx ?? fromDecl.info);
|
|
571
|
+
}
|
|
572
|
+
else if (isUpper(head) && lang !== 'go')
|
|
573
|
+
cur = { kind: 'type', name: head, ctx: fi };
|
|
574
|
+
else {
|
|
575
|
+
const fl = localType(cx, fi, topLevel(fi), head, lang);
|
|
576
|
+
if (fl?.type)
|
|
577
|
+
cur = typeFrom(cx, fl.type, lang, fi);
|
|
578
|
+
else
|
|
579
|
+
return unknown;
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
for (let i = 1; i < chain.length; i++) {
|
|
584
|
+
const seg = chain[i];
|
|
585
|
+
if (seg === '()')
|
|
586
|
+
continue;
|
|
587
|
+
const isCall = chain[i + 1] === '()';
|
|
588
|
+
if (!cur || cur.kind === 'unknown')
|
|
589
|
+
return unknown;
|
|
590
|
+
if (cur.kind === 'module') {
|
|
591
|
+
if (isUpper(seg) && lang !== 'go')
|
|
592
|
+
cur = { kind: 'type', name: seg, ctx: (cur.mod && file(cx, cur.mod)) || fi };
|
|
593
|
+
else if (isCall && cur.mod) {
|
|
594
|
+
const mf = lang === 'go' ? goDirFiles(cx, cur.mod) : [file(cx, cur.mod)].filter((f) => f != null);
|
|
595
|
+
cur = fnReturn(cx, mf, seg);
|
|
596
|
+
}
|
|
597
|
+
else if (lang === 'go' && cur.dir)
|
|
598
|
+
return unknown;
|
|
599
|
+
else if (cur.mod && lang === 'py' && resolvePy(cx.root, cur.mod, `.${seg}`, cx.read))
|
|
600
|
+
cur = { kind: 'module', mod: resolvePy(cx.root, cur.mod, `.${seg}`, cx.read) };
|
|
601
|
+
else
|
|
602
|
+
return unknown;
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
if (cur.kind !== 'type')
|
|
606
|
+
return unknown;
|
|
607
|
+
const d = cur.decl ?? findType(cx, cur.ctx, cur.name, cur.qual);
|
|
608
|
+
if (!d)
|
|
609
|
+
return cur.name === '#external' && !isCall ? cur : unknown;
|
|
610
|
+
if (isCall) {
|
|
611
|
+
cur = methodReturn(cx, d, seg);
|
|
612
|
+
continue;
|
|
613
|
+
}
|
|
614
|
+
const ft = fieldType(cx, d, seg);
|
|
615
|
+
if (!ft)
|
|
616
|
+
return unknown;
|
|
617
|
+
cur = typeFrom(cx, ft.type, lang, ft.ctx ?? d.info);
|
|
618
|
+
}
|
|
619
|
+
if (cur && cur.kind !== 'type' && chain.includes('()'))
|
|
620
|
+
return unknown;
|
|
621
|
+
return cur ?? unknown;
|
|
622
|
+
}
|
|
623
|
+
const TS_GLOBALS = new Set([
|
|
624
|
+
'Map',
|
|
625
|
+
'Set',
|
|
626
|
+
'WeakMap',
|
|
627
|
+
'WeakSet',
|
|
628
|
+
'Array',
|
|
629
|
+
'ReadonlyArray',
|
|
630
|
+
'ReadonlyMap',
|
|
631
|
+
'Promise',
|
|
632
|
+
'Date',
|
|
633
|
+
'RegExp',
|
|
634
|
+
'Error',
|
|
635
|
+
'Record',
|
|
636
|
+
]);
|
|
637
|
+
const tsMethods = (d) => [
|
|
638
|
+
...d.info.code
|
|
639
|
+
.slice(d.start, d.end)
|
|
640
|
+
.matchAll(/(?:^|[\n;{,])[ \t]*(?:readonly\s+)?([A-Za-z_$][\w$]*)\s*\??\s*(?::\s*)?(?:<[^>]*>\s*)?\(/g),
|
|
641
|
+
].map((m) => m[1]);
|
|
642
|
+
const pyMethods = (d) => [...d.info.code.slice(d.start, d.end).matchAll(/\n[ \t]+(?:async\s+)?def\s+(\w+)/g)].map((m) => m[1]);
|
|
643
|
+
export function reaches(cx, rt, ownerDecl, owner, method, ownerPath) {
|
|
644
|
+
if (rt.name === '#top')
|
|
645
|
+
return 'unsure';
|
|
646
|
+
if (rt.name === '#external')
|
|
647
|
+
return 'no';
|
|
648
|
+
const lang = langOf(ownerPath);
|
|
649
|
+
const ambiguous = (name) => lang === 'rs' && (global(cx, 'rs').types.get(name)?.length ?? 0) > 1;
|
|
650
|
+
const same = (a, b) => a.name === b.name && (lang === 'go' ? dirname(a.info.path) === dirname(b.info.path) : a.info.path === b.info.path);
|
|
651
|
+
const match = (a, d) => {
|
|
652
|
+
if (ambiguous(a.name))
|
|
653
|
+
return 'unsure';
|
|
654
|
+
if (a.d && d)
|
|
655
|
+
return same(a.d, d) ? 'yes' : 'no';
|
|
656
|
+
return lang === 'rs' && d ? 'yes' : 'unsure';
|
|
657
|
+
};
|
|
658
|
+
const rd = findType(cx, rt.ctx, rt.name, rt.qual);
|
|
659
|
+
if (rt.name === owner) {
|
|
660
|
+
if (!rd)
|
|
661
|
+
return 'unsure';
|
|
662
|
+
const own = lang === 'go' ? dirname(rd.info.path) === dirname(ownerPath) : rd.info.path === ownerPath || (lang === 'rs' && !ambiguous(owner));
|
|
663
|
+
if (own)
|
|
664
|
+
return 'yes';
|
|
665
|
+
if (rd.kind !== 'interface')
|
|
666
|
+
return 'no';
|
|
667
|
+
}
|
|
668
|
+
if (!ownerDecl)
|
|
669
|
+
return 'unsure';
|
|
670
|
+
const anc = ancestors(cx, ownerDecl);
|
|
671
|
+
for (const a of anc.filter((x) => x.name === rt.name)) {
|
|
672
|
+
const r = match(a, rd);
|
|
673
|
+
if (r !== 'no')
|
|
674
|
+
return r;
|
|
675
|
+
}
|
|
676
|
+
if (!rd) {
|
|
677
|
+
if (lang === 'ts' && TS_GLOBALS.has(rt.name) && !rt.qual && !bindings(cx, rt.ctx).has(rt.name))
|
|
678
|
+
return 'no';
|
|
679
|
+
const nominal = (lang === 'java' || lang === 'cs') &&
|
|
680
|
+
!global(cx, lang).types.has(rt.name) &&
|
|
681
|
+
anc.every((a) => a.d) &&
|
|
682
|
+
!new RegExp(`<[^<>()]*\\b${esc(rt.name)}\\b`).test(rt.ctx?.code ?? '') &&
|
|
683
|
+
(lang === 'cs' || new RegExp(`^\\s*import\\s+[\\w.]+\\.${esc(rt.name)}\\s*;`, 'm').test(rt.ctx?.keep ?? ''));
|
|
684
|
+
return nominal ? 'no' : 'unsure';
|
|
685
|
+
}
|
|
686
|
+
if (lang === 'go' && rd.kind === 'interface') {
|
|
687
|
+
const ra = ancestors(cx, rd);
|
|
688
|
+
const need = [...goIfaceMethods(rd), ...ra.flatMap((a) => (a.d ? goIfaceMethods(a.d) : []))];
|
|
689
|
+
const have = new Set([...goMethods(cx, ownerDecl), ...anc.flatMap((a) => (a.d ? [...goMethods(cx, a.d)] : []))]);
|
|
690
|
+
const needOpen = ra.some((a) => !a.d);
|
|
691
|
+
const haveOpen = anc.some((a) => !a.d);
|
|
692
|
+
if (need.some((x) => !have.has(x)) && !haveOpen)
|
|
693
|
+
return 'no';
|
|
694
|
+
if (!need.includes(method) && !needOpen)
|
|
695
|
+
return 'no';
|
|
696
|
+
return needOpen || haveOpen ? 'unsure' : 'yes';
|
|
697
|
+
}
|
|
698
|
+
const shape = lang === 'ts' && rd.kind === 'interface'
|
|
699
|
+
? tsMethods(rd)
|
|
700
|
+
: lang === 'py' && ancestors(cx, rd).some((a) => a.name === 'Protocol')
|
|
701
|
+
? pyMethods(rd)
|
|
702
|
+
: null;
|
|
703
|
+
if (shape?.includes(method) && shape.every((m) => defines(cx, ownerDecl, m)))
|
|
704
|
+
return 'unsure';
|
|
705
|
+
if (defines(cx, rd, method) && rd.info.path !== ownerPath)
|
|
706
|
+
return 'no';
|
|
707
|
+
for (const a of ancestors(cx, rd)) {
|
|
708
|
+
if (a.name === owner) {
|
|
709
|
+
const r = match(a, ownerDecl);
|
|
710
|
+
if (r !== 'no')
|
|
711
|
+
return r;
|
|
712
|
+
}
|
|
713
|
+
if (a.d && defines(cx, a.d, method) && a.d.info.path !== ownerPath)
|
|
714
|
+
return 'no';
|
|
715
|
+
}
|
|
716
|
+
return 'no';
|
|
717
|
+
}
|
|
718
|
+
export function inTypePosition(body, idx, lang) {
|
|
719
|
+
const pre = body.slice(Math.max(0, idx - 120), idx);
|
|
720
|
+
if (lang !== 'ts' && lang !== 'py' && lang !== 'rs')
|
|
721
|
+
return false;
|
|
722
|
+
if (/[\w$\]}]\s*\??\s*:\s*$/.test(pre) && openBracket(body, idx) === '(')
|
|
723
|
+
return true;
|
|
724
|
+
if (/\b(const|let|var)\s+[\w$]+\s*:\s*$/.test(pre))
|
|
725
|
+
return true;
|
|
726
|
+
if (/\)[ \t]*->[ \t]*$/.test(pre) || (lang === 'ts' && /\)[ \t]*:[ \t]*$/.test(pre)))
|
|
727
|
+
return true;
|
|
728
|
+
if (lang === 'ts' && /\bas\s+$/.test(pre))
|
|
729
|
+
return true;
|
|
730
|
+
if (lang === 'py' && /(^|\n)[ \t]*[\w.]+\s*:\s*$/.test(pre))
|
|
731
|
+
return true;
|
|
732
|
+
return /:\s*[\w$.]+<\s*$/.test(pre) || /(Optional|List)\[\s*$/.test(pre);
|
|
733
|
+
}
|