flowfig 0.7.0 → 0.8.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 +55 -23
- package/dist/check.js +68 -6
- package/dist/cli.js +24 -13
- package/dist/code.d.ts +26 -0
- package/dist/code.js +568 -0
- package/dist/diff.js +12 -5
- package/dist/draw.js +1 -1
- package/dist/edges.d.ts +7 -0
- package/dist/edges.js +557 -0
- package/dist/geometry.d.ts +11 -2
- package/dist/geometry.js +255 -67
- package/dist/guide.d.ts +22 -2
- package/dist/guide.js +282 -219
- package/dist/imports.d.ts +11 -0
- package/dist/imports.js +235 -0
- package/dist/index.js +36 -28
- package/dist/load.d.ts +2 -0
- package/dist/load.js +24 -1
- package/dist/mcp.d.ts +7 -0
- package/dist/mcp.js +22 -9
- package/dist/model.d.ts +13 -2
- package/dist/model.js +54 -11
- package/dist/scene.d.ts +2 -0
- package/dist/source.d.ts +1 -1
- package/dist/source.js +3 -3
- package/dist/svg.js +49 -32
- 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 +25 -3
- package/dist/verify.js +67 -22
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
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
|
+
}
|