ucn 5.5.0 → 5.5.2
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/.claude/skills/ucn/SKILL.md +35 -9
- package/.claude/skills/ucn/references/commands.md +38 -16
- package/.claude/skills/ucn/references/resolution-rules.md +126 -20
- package/.claude/skills/ucn/references/trust-contract.md +5 -3
- package/README.md +41 -27
- package/cli/index.js +6 -1
- package/core/accessors.js +341 -54
- package/core/account.js +106 -31
- package/core/analysis.js +266 -127
- package/core/ast-analysis.js +12 -3
- package/core/brief.js +8 -4
- package/core/build-worker.js +2 -1
- package/core/cache.js +144 -6
- package/core/call-name-cache.js +134 -0
- package/core/callable-scope.js +21 -0
- package/core/callers.js +5111 -1024
- package/core/check.js +22 -5
- package/core/class-identity.js +399 -12
- package/core/constructor-calls.js +831 -0
- package/core/constructor-hierarchy.js +147 -0
- package/core/constructor-protocol.js +196 -0
- package/core/contract-membership.js +346 -140
- package/core/cpp-conversion-constraints.js +123 -0
- package/core/cpp-conversion-syntax.js +216 -0
- package/core/cpp-conversions.js +414 -0
- package/core/cpp-scope.js +245 -44
- package/core/deadcode.js +35 -15
- package/core/decorator-binding.js +137 -3
- package/core/enum-reference-syntax.js +134 -0
- package/core/execute.js +17 -1
- package/core/external-macros.js +42 -10
- package/core/go-packages.js +171 -0
- package/core/graph-build.js +2 -0
- package/core/graph.js +75 -59
- package/core/ignore-rules.js +2 -1
- package/core/imports.js +145 -19
- package/core/index-ir.js +41 -4
- package/core/ir.js +27 -6
- package/core/macro-expansion.js +38 -2
- package/core/out-of-class-members.js +182 -0
- package/core/output/analysis-ext.js +4 -2
- package/core/output/analysis.js +89 -20
- package/core/output/graph.js +12 -3
- package/core/output/lines.js +4 -3
- package/core/output/public.js +1 -0
- package/core/output/reporting.js +6 -1
- package/core/output/shared.js +2 -0
- package/core/output/tracing.js +28 -22
- package/core/output-budget.js +2 -2
- package/core/parallel-build.js +52 -1
- package/core/project.js +228 -114
- package/core/provenance-constructor-base.js +161 -0
- package/core/provenance-facts.js +257 -27
- package/core/provenance-overload.js +458 -54
- package/core/provenance-type.js +487 -0
- package/core/provenance.js +234 -15
- package/core/public-command.js +17 -2
- package/core/query-bodies.js +159 -0
- package/core/reflection.js +255 -2
- package/core/rename-token-syntax.js +111 -0
- package/core/reporting.js +13 -7
- package/core/rust-macro-expansion.js +1202 -157
- package/core/rust-modules.js +21 -3
- package/core/rust-names.js +607 -0
- package/core/search.js +70 -17
- package/core/shared.js +98 -1
- package/core/source-memo.js +201 -0
- package/core/text-usage-syntax.js +38 -0
- package/core/tracing.js +7 -1
- package/core/type-denotation.js +157 -46
- package/core/type-references.js +615 -246
- package/core/value-references.js +2777 -0
- package/core/verify.js +1097 -281
- package/languages/adapter.js +28 -4
- package/languages/c-family.js +1118 -164
- package/languages/constructor-bases.js +105 -0
- package/languages/cpp-platform.js +26 -0
- package/languages/cpp-site-syntax.js +83 -0
- package/languages/csharp-scope.js +439 -0
- package/languages/csharp.js +428 -68
- package/languages/go.js +385 -207
- package/languages/html.js +4 -2
- package/languages/index.js +397 -2
- package/languages/java-platform.js +226 -0
- package/languages/java-unicode.js +269 -0
- package/languages/java.js +461 -117
- package/languages/javascript.js +418 -66
- package/languages/lexical-scope.js +190 -23
- package/languages/python.js +404 -54
- package/languages/rust.js +548 -89
- package/languages/utils.js +67 -8
- package/mcp/server.js +8 -2
- package/package.json +2 -2
|
@@ -177,12 +177,16 @@ function returnedDecorator(fnNode, family) {
|
|
|
177
177
|
/**
|
|
178
178
|
* Resolve a decorator's head name to one project callable definition, or null.
|
|
179
179
|
*/
|
|
180
|
-
function projectDecoratorDef(index, def, head, rest) {
|
|
180
|
+
function projectDecoratorDef(index, def, head, rest, implementation = false) {
|
|
181
181
|
const { _nameBindingReaches, _moduleAttributeBindingReaches } = require('./callers');
|
|
182
182
|
const target = rest ? rest.slice(1) : head;
|
|
183
183
|
if (rest && rest.includes('.', 1)) return null;
|
|
184
|
+
// `@overload` stubs declare signatures of the implementation beside
|
|
185
|
+
// them; the implementation decides what a call returns (fix #434).
|
|
184
186
|
const candidates = (index.symbols.get(target) || []).filter(candidate =>
|
|
185
|
-
!candidate.className && /function|method/.test(candidate.type || 'function')
|
|
187
|
+
!candidate.className && /function|method/.test(candidate.type || 'function') &&
|
|
188
|
+
!(implementation && (candidate.decorators || []).some(name =>
|
|
189
|
+
/^(typing\.)?overload$/.test(String(typeof name === 'string' ? name : name?.name || '')))));
|
|
186
190
|
const reaching = candidates.filter(candidate => {
|
|
187
191
|
const targets = new Set([candidate.file]);
|
|
188
192
|
if (!rest) {
|
|
@@ -257,4 +261,134 @@ function nameBindingDecorators(index, def, memo = new Map()) {
|
|
|
257
261
|
return unproven;
|
|
258
262
|
}
|
|
259
263
|
|
|
260
|
-
|
|
264
|
+
// A Python `if` condition testing the parameter against None: true for
|
|
265
|
+
// `p is None`, false for `p is not None`, null for anything else.
|
|
266
|
+
function noneTest(condition, p) {
|
|
267
|
+
if (condition?.type !== 'comparison_operator' || condition.namedChildCount !== 2) return null;
|
|
268
|
+
const [left, right] = condition.namedChildren;
|
|
269
|
+
if (left.type !== 'identifier' || left.text !== p || right.type !== 'none') return null;
|
|
270
|
+
const operators = condition.children.filter(child => !child.isNamed).map(child => child.type);
|
|
271
|
+
if (operators.length === 1 && operators[0] === 'is') return true;
|
|
272
|
+
if (operators.length === 2 && operators[0] === 'is' && operators[1] === 'not') return false;
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* The return statements of a Python function body that run when the
|
|
278
|
+
* parameter `p` is None (`pIsNone`) or is a class (fix #434): branches of
|
|
279
|
+
* `if p is None` / `if p is not None` the value rules out are skipped;
|
|
280
|
+
* nested definitions, lambdas and classes run elsewhere.
|
|
281
|
+
*/
|
|
282
|
+
function pythonReturnsUnder(block, p, pIsNone, out = []) {
|
|
283
|
+
for (const statement of namedChildrenOf(block)) {
|
|
284
|
+
if (statement.type === 'return_statement') { out.push(statement); continue; }
|
|
285
|
+
if (/^(function_definition|class_definition|decorated_definition)$/.test(statement.type)) continue;
|
|
286
|
+
if (statement.type === 'if_statement') {
|
|
287
|
+
const test = noneTest(statement.childForFieldName('condition'), p);
|
|
288
|
+
if (test !== null) {
|
|
289
|
+
if (test === pIsNone) pythonReturnsUnder(statement.childForFieldName('consequence'), p, pIsNone, out);
|
|
290
|
+
else {
|
|
291
|
+
for (const alternative of namedChildrenOf(statement).filter(child =>
|
|
292
|
+
child.type === 'else_clause' || child.type === 'elif_clause')) {
|
|
293
|
+
if (alternative.type === 'elif_clause') return null;
|
|
294
|
+
pythonReturnsUnder(alternative.childForFieldName('body'), p, pIsNone, out);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
let invalid = false;
|
|
301
|
+
traverseTree(statement, node => {
|
|
302
|
+
if (invalid) return false;
|
|
303
|
+
if (/^(function_definition|class_definition|lambda)$/.test(node.type)) return false;
|
|
304
|
+
if (node.type === 'return_statement') out.push(node);
|
|
305
|
+
if (node.type === 'yield') invalid = true;
|
|
306
|
+
return true;
|
|
307
|
+
});
|
|
308
|
+
if (invalid) return null;
|
|
309
|
+
}
|
|
310
|
+
return out;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Nested function definitions of a body, by name (not inside other defs). */
|
|
314
|
+
function nestedDefinitions(fnNode) {
|
|
315
|
+
const nested = new Map();
|
|
316
|
+
const body = fnNode.childForFieldName('body');
|
|
317
|
+
traverseTree(body, node => {
|
|
318
|
+
if (sameNode(node, body)) return true;
|
|
319
|
+
const definition = node.type === 'decorated_definition' ? node.childForFieldName('definition') : node;
|
|
320
|
+
if (definition?.type === 'function_definition') {
|
|
321
|
+
const name = definition.childForFieldName('name');
|
|
322
|
+
if (name && node.type === 'function_definition') nested.set(name.text, definition);
|
|
323
|
+
return false;
|
|
324
|
+
}
|
|
325
|
+
return !/^(class_definition|lambda)$/.test(node.type);
|
|
326
|
+
});
|
|
327
|
+
return nested;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Python: decorator `fnNode` applied to a class returns that class (fix
|
|
332
|
+
* #434): every return reachable when its first parameter holds the class
|
|
333
|
+
* returns the parameter, or passes it first to a nested function that
|
|
334
|
+
* does. `pIsNone` evaluates the factory form (`@d(..)` with the class
|
|
335
|
+
* parameter left at its None default): every reachable return then gives
|
|
336
|
+
* a decorator that returns its argument.
|
|
337
|
+
*/
|
|
338
|
+
function pythonReturnsItsClass(fnNode, scope, depth = 0, pIsNone = false) {
|
|
339
|
+
if (!fnNode || depth > 4) return false;
|
|
340
|
+
const p = firstParamName(fnNode, 'python');
|
|
341
|
+
const body = fnNode.childForFieldName('body');
|
|
342
|
+
if (!p || !body) return false;
|
|
343
|
+
const nested = new Map([...scope, ...nestedDefinitions(fnNode)]);
|
|
344
|
+
const returns = pythonReturnsUnder(body, p, pIsNone);
|
|
345
|
+
if (!returns || returns.length === 0) return false;
|
|
346
|
+
return returns.every(statement => {
|
|
347
|
+
const value = statement.namedChild(0);
|
|
348
|
+
if (pIsNone) {
|
|
349
|
+
const decorator = value?.type === 'identifier' ? nested.get(value.text) : null;
|
|
350
|
+
return !!decorator && pythonReturnsItsClass(decorator, nested, depth + 1);
|
|
351
|
+
}
|
|
352
|
+
if (value?.type === 'identifier') return value.text === p;
|
|
353
|
+
if (value?.type !== 'call') return false;
|
|
354
|
+
const callee = value.childForFieldName('function');
|
|
355
|
+
const first = namedChildrenOf(value.childForFieldName('arguments'))[0];
|
|
356
|
+
const inner = callee?.type === 'identifier' ? nested.get(callee.text) : null;
|
|
357
|
+
return !!inner && first?.type === 'identifier' && first.text === p &&
|
|
358
|
+
pythonReturnsItsClass(inner, nested, depth + 1);
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Whether a project class decorator returns the class it decorates (fix
|
|
364
|
+
* #434): `@register` / `@rich_repr` / `@rich_repr(angular=True)` keep the
|
|
365
|
+
* class (and its synthesized initializer) in place.
|
|
366
|
+
*/
|
|
367
|
+
function classPreservingDecorator(index, classDef, text) {
|
|
368
|
+
const fileEntry = index.files.get(classDef.file);
|
|
369
|
+
if (fileEntry?.language !== 'python') return false;
|
|
370
|
+
const call = text.indexOf('(');
|
|
371
|
+
const expr = (call >= 0 ? text.slice(0, call) : text).trim();
|
|
372
|
+
if (!/^[A-Za-z_][\w]*(?:\.[A-Za-z_][\w]*)?$/.test(expr)) return false;
|
|
373
|
+
const dot = expr.indexOf('.');
|
|
374
|
+
const head = dot < 0 ? expr : expr.slice(0, dot);
|
|
375
|
+
const rest = dot < 0 ? '' : expr.slice(dot);
|
|
376
|
+
const target = projectDecoratorDef(index, classDef, head, rest, true);
|
|
377
|
+
if (!target) return false;
|
|
378
|
+
const tree = treeOf(index, target.file);
|
|
379
|
+
const fnNode = tree ? functionNodeAt(tree, target.nameLine || target.startLine, target.name) : null;
|
|
380
|
+
if (!fnNode) return false;
|
|
381
|
+
if (call < 0) return pythonReturnsItsClass(fnNode, new Map());
|
|
382
|
+
// The factory form: the class parameter is left at its None default
|
|
383
|
+
// when no positional argument is passed.
|
|
384
|
+
const args = text.slice(call + 1, text.lastIndexOf(')')).trim();
|
|
385
|
+
const positional = args.length > 0 && args.split(',').some(arg => !/^\s*[A-Za-z_]\w*\s*=/.test(arg));
|
|
386
|
+
const firstParam = namedChildrenOf(fnNode.childForFieldName('parameters'))[0];
|
|
387
|
+
if (!positional && firstParam?.type === 'default_parameter' &&
|
|
388
|
+
firstParam.childForFieldName('value')?.type === 'none') {
|
|
389
|
+
return pythonReturnsItsClass(fnNode, new Map(), 0, true);
|
|
390
|
+
}
|
|
391
|
+
return pythonReturnsItsClass(returnedDecorator(fnNode, 'python'), new Map());
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
module.exports = { nameBindingDecorators, classPreservingDecorator };
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { langTraits } = require('../languages');
|
|
4
|
+
const cache = require('./cache');
|
|
5
|
+
const { indexedContent, parseViewParts, syntaxMode } = require('./source-memo');
|
|
6
|
+
const { TypeReferenceResolver } = require('./type-references');
|
|
7
|
+
const { classDefsNamed, findDeclaringClass } = require('./class-identity');
|
|
8
|
+
|
|
9
|
+
function validScope(value) {
|
|
10
|
+
return value && typeof value.namespace === 'string' &&
|
|
11
|
+
Number.isInteger(value.startIndex) && Number.isInteger(value.endIndex) &&
|
|
12
|
+
value.startIndex >= 0 && value.endIndex >= value.startIndex;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function validContext(value) {
|
|
16
|
+
return value == null || Array.isArray(value.scopes) && value.scopes.every(validScope) &&
|
|
17
|
+
Array.isArray(value.directives) && value.directives.every(detail => detail &&
|
|
18
|
+
typeof detail.type === 'string' && typeof detail.module === 'string' &&
|
|
19
|
+
Array.isArray(detail.names) && detail.names.every(name => typeof name === 'string') &&
|
|
20
|
+
(detail.namespace == null || typeof detail.namespace === 'string') &&
|
|
21
|
+
['static', 'global', 'rooted'].every(key => detail[key] == null || typeof detail[key] === 'boolean') &&
|
|
22
|
+
Array.isArray(detail.usingScopes) && detail.usingScopes.every(validScope));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Persist syntax only; every owner and member verdict is recomputed. */
|
|
26
|
+
class EnumReferenceSyntax {
|
|
27
|
+
constructor(resolver) { this.resolver = resolver; this.states = new Map(); }
|
|
28
|
+
|
|
29
|
+
collecting(file) { return this.states.get(file)?.valid === true; }
|
|
30
|
+
|
|
31
|
+
capture(file, fact) {
|
|
32
|
+
const state = this.states.get(file);
|
|
33
|
+
if (!state?.valid) return;
|
|
34
|
+
if (!fact) state.valid = false;
|
|
35
|
+
else state.facts.push(fact);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
read(file, entry, language, rows, content) {
|
|
39
|
+
const r = this.resolver, index = r.index;
|
|
40
|
+
if (file === r.def.file || r.owner?.def?.type !== 'enum' ||
|
|
41
|
+
!langTraits(language)?.staticEnumReferenceSyntax || !entry.hash ||
|
|
42
|
+
!index._usageResultCache || index.isProvenanceFactIndex ||
|
|
43
|
+
!indexedContent(index, file, entry, content)) return null;
|
|
44
|
+
const mode = syntaxMode('enumSyntax', [...parseViewParts(entry, language), rows]);
|
|
45
|
+
this.states.set(file, { valid: true, facts: [], mode });
|
|
46
|
+
const value = cache.getUsageResult(index, file, r.name, mode);
|
|
47
|
+
if (!Array.isArray(value) || value.length !== 3 || value[0] !== 2 ||
|
|
48
|
+
!Array.isArray(value[1]) || !Array.isArray(value[2])) return null;
|
|
49
|
+
const lines = new Map(), wanted = new Set(rows);
|
|
50
|
+
for (const record of value[1]) {
|
|
51
|
+
if (!Array.isArray(record) || record.length !== 4 || !wanted.has(record[0]) ||
|
|
52
|
+
lines.has(record[0]) || !Number.isInteger(record[1]) || record[1] < 0 ||
|
|
53
|
+
typeof record[2] !== 'boolean' || record[3] !== 0) return null;
|
|
54
|
+
lines.set(record[0], { edits: [], reviews: [], skips: 0, skipColumns: [],
|
|
55
|
+
text: record[2], tokens: record[1], macroBody: false });
|
|
56
|
+
}
|
|
57
|
+
if (lines.size !== rows.length) return null;
|
|
58
|
+
const counts = new Map(), references = [];
|
|
59
|
+
for (const fact of value[2]) {
|
|
60
|
+
if (!fact || !wanted.has(fact.line) || !Number.isInteger(fact.column) || fact.column < 0 ||
|
|
61
|
+
!Number.isInteger(fact.at) || fact.at < 0 || content.slice(fact.at, fact.at + r.name.length) !== r.name ||
|
|
62
|
+
!['member', 'type', 'opaque'].includes(fact.kind) || typeof fact.accessKind !== 'string' ||
|
|
63
|
+
fact.kind !== 'opaque' && (typeof fact.receiver !== 'string' || !fact.receiver) ||
|
|
64
|
+
fact.kind === 'opaque' && (!['macro', 'template'].includes(fact.sourceRole) ||
|
|
65
|
+
fact.receiver != null || fact.context != null || fact.owner != null) ||
|
|
66
|
+
!validContext(fact.context) || fact.owner != null &&
|
|
67
|
+
(typeof fact.owner.name !== 'string' || !Number.isInteger(fact.owner.startLine) ||
|
|
68
|
+
!Number.isInteger(fact.owner.endLine) || fact.owner.startLine < 1 ||
|
|
69
|
+
fact.owner.endLine < fact.owner.startLine)) return null;
|
|
70
|
+
if (fact.kind !== 'opaque' && langTraits(language).enumReferenceContext && fact.context == null) return null;
|
|
71
|
+
counts.set(fact.line, (counts.get(fact.line) || 0) + 1);
|
|
72
|
+
}
|
|
73
|
+
for (const [line, slot] of lines) if ((counts.get(line) || 0) !== slot.tokens) return null;
|
|
74
|
+
for (const fact of value[2]) {
|
|
75
|
+
const lookup = fact.kind === 'opaque' ? null : this.ownerOf(file, language, fact);
|
|
76
|
+
if (fact.kind !== 'opaque' && !lookup) return null;
|
|
77
|
+
const verdict = fact.kind === 'opaque'
|
|
78
|
+
? { verdict: 'unknown', reason: 'value-binding-unresolved' } : r.memberAt(lookup.owner);
|
|
79
|
+
const slot = lines.get(fact.line);
|
|
80
|
+
if (verdict.verdict === 'yes') slot.edits.push(fact.column);
|
|
81
|
+
else if (verdict.verdict === 'unknown') slot.reviews.push({ column: fact.column, reason: verdict.reason });
|
|
82
|
+
else { slot.skips++; slot.skipColumns.push(fact.column); }
|
|
83
|
+
references.push({ file: entry.relativePath, absoluteFile: file,
|
|
84
|
+
line: fact.line, column: fact.column, expression: index.getLineContent(file, fact.line).trim(),
|
|
85
|
+
...(fact.kind === 'member' && { receiver: fact.receiver }), accessKind: fact.accessKind,
|
|
86
|
+
verdict: verdict.verdict, reason: verdict.reason });
|
|
87
|
+
}
|
|
88
|
+
r.references.push(...references);
|
|
89
|
+
return lines;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
ownerOf(file, language, fact) {
|
|
93
|
+
const r = this.resolver, index = r.index;
|
|
94
|
+
if (fact.kind === 'member' && langTraits(language).bareCallReachesMethods) {
|
|
95
|
+
let enclosing = null;
|
|
96
|
+
if (fact.owner) {
|
|
97
|
+
const entries = classDefsNamed(index, fact.owner.name).entries.filter(e =>
|
|
98
|
+
e.def.file === file && e.def.startLine <= fact.owner.startLine &&
|
|
99
|
+
e.def.endLine >= fact.owner.endLine);
|
|
100
|
+
if (new Set(entries.map(e => e.key)).size !== 1) return null;
|
|
101
|
+
enclosing = { name: entries[0].def.name, key: entries[0].key, def: entries[0].def };
|
|
102
|
+
} else if (index.findEnclosingFunction(file, fact.line, true)?.className) return null;
|
|
103
|
+
const members = (index.symbols.get(fact.receiver) || []).filter(d => d.className);
|
|
104
|
+
if (enclosing && findDeclaringClass(index, enclosing, members)) return { owner: null };
|
|
105
|
+
}
|
|
106
|
+
if ((index.symbols.get(fact.receiver) || []).some(d => d.type === 'type')) return null;
|
|
107
|
+
const matches = [];
|
|
108
|
+
for (const entry of classDefsNamed(index, fact.receiver).entries) {
|
|
109
|
+
// Same-file declarations also need the native lexical scope relation.
|
|
110
|
+
if (entry.def.file === file) return null;
|
|
111
|
+
const sub = new TypeReferenceResolver(index, entry.def, fact.receiver);
|
|
112
|
+
sub.siteFile = file;
|
|
113
|
+
if (fact.context) sub.siteSyntax = { file, line: fact.line, arity: 0, context: fact.context };
|
|
114
|
+
const verdict = sub.resolveName(file, fact.line, fact.receiver);
|
|
115
|
+
if (verdict.verdict === 'unknown') return { owner: null };
|
|
116
|
+
if (verdict.verdict === 'yes') matches.push(entry);
|
|
117
|
+
}
|
|
118
|
+
if (new Set(matches.map(e => e.key)).size !== 1) return { owner: null };
|
|
119
|
+
return { owner: { name: matches[0].def.name, key: matches[0].key, def: matches[0].def } };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
remember(file, lines) {
|
|
123
|
+
const state = this.states.get(file);
|
|
124
|
+
if (!state?.valid) return;
|
|
125
|
+
const counts = new Map();
|
|
126
|
+
for (const fact of state.facts) counts.set(fact.line, (counts.get(fact.line) || 0) + 1);
|
|
127
|
+
if ([...lines].some(([line, slot]) => slot.macroBody || slot.stringAnnotation ||
|
|
128
|
+
slot.tokens !== (counts.get(line) || 0) || slot.tokens === 0 && !slot.text)) return;
|
|
129
|
+
const value = [2, [...lines].map(([line, slot]) => [line, slot.tokens, slot.text, 0]), state.facts];
|
|
130
|
+
cache.storeUsageResult(this.resolver.index, file, this.resolver.name, state.mode, value);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = { EnumReferenceSyntax };
|
package/core/execute.js
CHANGED
|
@@ -16,6 +16,7 @@ const { addTestExclusions, pickBestDefinition, parseSymbolHandle, looksLikeHandl
|
|
|
16
16
|
const { cleanHtmlScriptTags, detectLanguage } = require('./parser');
|
|
17
17
|
const { renderExpandItem } = require('./expand-cache');
|
|
18
18
|
const { CANONICAL_COMMANDS } = require('./registry');
|
|
19
|
+
const { sourceRangeTarget } = require('./public-command');
|
|
19
20
|
const { isAccessorDefinition } = require('./accessors');
|
|
20
21
|
const { describeError } = require('./errors');
|
|
21
22
|
|
|
@@ -1547,7 +1548,7 @@ const HANDLERS = {
|
|
|
1547
1548
|
p.name.includes('/') || p.name.includes('\\') ||
|
|
1548
1549
|
/\.(js|ts|py|go|java|rs)$/.test(p.name));
|
|
1549
1550
|
if (!testsTargetIsFile) applyClassMethodSyntax(p);
|
|
1550
|
-
if (
|
|
1551
|
+
if (!testsTargetIsFile && p.line) {
|
|
1551
1552
|
const line = Number(p.line);
|
|
1552
1553
|
const resolvedFile = p.file ? index.resolveFilePathForQuery(p.file) : null;
|
|
1553
1554
|
const pinned = (index.symbols.get(p.name) || []).filter(d =>
|
|
@@ -1589,6 +1590,9 @@ const HANDLERS = {
|
|
|
1589
1590
|
const result = index.tests(p.name, {
|
|
1590
1591
|
callsOnly: p.callsOnly || false,
|
|
1591
1592
|
className: p.className,
|
|
1593
|
+
// A line pin names one definition, including the file/line
|
|
1594
|
+
// parameters passed by a composed command such as show.
|
|
1595
|
+
...(!testsTargetIsFile && p.line && testsResolution?.def && { definition: testsResolution.def }),
|
|
1592
1596
|
// Bare ambiguous names use the same deterministic definition as
|
|
1593
1597
|
// the rest of the semantic surface. The warning below discloses
|
|
1594
1598
|
// that choice and gives the caller a stable file= escape hatch.
|
|
@@ -1681,6 +1685,7 @@ const HANDLERS = {
|
|
|
1681
1685
|
if (result.excludedDecorated != null) sliced.excludedDecorated = result.excludedDecorated;
|
|
1682
1686
|
if (result.excludedExternalContract != null) sliced.excludedExternalContract = result.excludedExternalContract;
|
|
1683
1687
|
if (result.excludedRuntimeContract != null) sliced.excludedRuntimeContract = result.excludedRuntimeContract;
|
|
1688
|
+
if (result.excludedConstructors != null) sliced.excludedConstructors = result.excludedConstructors;
|
|
1684
1689
|
if (result.excludedDynamicDispatch != null) sliced.excludedDynamicDispatch = result.excludedDynamicDispatch;
|
|
1685
1690
|
if (result.pythonImplicitExportFiles != null) sliced.pythonImplicitExportFiles = result.pythonImplicitExportFiles;
|
|
1686
1691
|
if (result.computedDispatch != null) sliced.computedDispatch = result.computedDispatch;
|
|
@@ -2515,9 +2520,18 @@ function execute(index, command, params = {}) {
|
|
|
2515
2520
|
// logged params object; a frozen object silently no-oped the
|
|
2516
2521
|
// normalizers and returned wrong not-found answers).
|
|
2517
2522
|
params = { ...params };
|
|
2523
|
+
if (command === 'source') params = sourceRangeTarget(params);
|
|
2518
2524
|
// Listing completeness belongs to execution, including direct API calls.
|
|
2519
2525
|
if (params.lines) params.all = true;
|
|
2526
|
+
// Composed commands share one source snapshot and its derived facts.
|
|
2527
|
+
// Child operations still balance their scopes; the outer command owns
|
|
2528
|
+
// the lifetime so summary/relationships/source do not repeat reads.
|
|
2529
|
+
let operation = false;
|
|
2520
2530
|
try {
|
|
2531
|
+
if (typeof index?._beginOp === 'function' && typeof index?._endOp === 'function') {
|
|
2532
|
+
index._beginOp();
|
|
2533
|
+
operation = true;
|
|
2534
|
+
}
|
|
2521
2535
|
if (PUBLIC_COMMAND_SET.has(command)) {
|
|
2522
2536
|
const validationError = validatePublicParams(command, params);
|
|
2523
2537
|
if (validationError) return { ok: false, error: validationError };
|
|
@@ -2564,6 +2578,8 @@ function execute(index, command, params = {}) {
|
|
|
2564
2578
|
// A deliberate refusal keeps its message; an exception inside the
|
|
2565
2579
|
// engine is marked internal on every surface (fix #394).
|
|
2566
2580
|
return { ok: false, ...describeError(e) };
|
|
2581
|
+
} finally {
|
|
2582
|
+
if (operation) index._endOp();
|
|
2567
2583
|
}
|
|
2568
2584
|
}
|
|
2569
2585
|
|
package/core/external-macros.js
CHANGED
|
@@ -115,7 +115,7 @@ function leadsWithProjectMacro(body, perName) {
|
|
|
115
115
|
// `catch (...) { }` / `try`: statement fragments are read by the probe
|
|
116
116
|
// even though they hold braces.
|
|
117
117
|
function mayBeStatementFragment(body) {
|
|
118
|
-
return /^(?:try|catch|if)\b/.test(body.trim());
|
|
118
|
+
return /^(?:try|catch|if|while|for|switch)\b/.test(body.trim());
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
/**
|
|
@@ -166,24 +166,53 @@ function projectMacroDictionary(index, files) {
|
|
|
166
166
|
if (!readingMemo.has(key)) readingMemo.set(key, compute());
|
|
167
167
|
return readingMemo.get(key);
|
|
168
168
|
};
|
|
169
|
+
// An object-like body naming another project macro that reads as
|
|
170
|
+
// decoration reads as the rest of it (fix #427).
|
|
171
|
+
const { withoutDecorationMacros } = require('../languages/c-family');
|
|
172
|
+
const objectReading = new Map();
|
|
173
|
+
const objectReadingOf = name => {
|
|
174
|
+
if (objectReading.has(name)) return objectReading.get(name);
|
|
175
|
+
objectReading.set(name, null); // in progress
|
|
176
|
+
const slot = perName.get(name);
|
|
177
|
+
let reading = null;
|
|
178
|
+
if (slot && slot.objects.size > 0 && slot.functions.size === 0 && !slot.objects.has(null)) {
|
|
179
|
+
const bodies = [...slot.objects].sort(codeUnitCompare).map(body => withoutDecorationMacros(body,
|
|
180
|
+
word => word !== name && perName.has(word) && objectReadingOf(word) === 'decoration'));
|
|
181
|
+
if (bodies.every(body => !body || maySpecify(body) || mayBeStatementFragment(body)) &&
|
|
182
|
+
!bodies.some(body => leadsWithProjectMacro(body, perName))) {
|
|
183
|
+
reading = read(`o\0${bodies.join('\u0001')}`, () => objectMacroReading(parser, bodies));
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
objectReading.set(name, reading);
|
|
187
|
+
return reading;
|
|
188
|
+
};
|
|
169
189
|
const objectBodies = [];
|
|
170
190
|
const functionMacros = [];
|
|
191
|
+
// Function-like macros whose every expansion ends in a function head
|
|
192
|
+
// (fix #427): an invocation followed by a body defines the function the
|
|
193
|
+
// expansion names (`DEF_PRIMITIVE(name) { .. }`).
|
|
194
|
+
const functionHeads = [];
|
|
195
|
+
const { endsFunctionHead } = require('../languages/c-family');
|
|
171
196
|
for (const name of [...perName.keys()].sort(codeUnitCompare)) {
|
|
172
197
|
const slot = perName.get(name);
|
|
173
198
|
// Every definition of the name is one kind; object bodies are short.
|
|
174
199
|
if (slot.objects.size > 0 && slot.functions.size > 0) continue;
|
|
175
200
|
if (slot.objects.size > 0) {
|
|
176
201
|
if (slot.objects.has(null)) continue;
|
|
177
|
-
const
|
|
178
|
-
if (
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
202
|
+
const reading = objectReadingOf(name);
|
|
203
|
+
if (reading === 'decoration' || reading === 'statement' || reading === 'if') {
|
|
204
|
+
objectBodies.push([name, [...slot.objects].sort(codeUnitCompare).map(body => withoutDecorationMacros(body,
|
|
205
|
+
word => word !== name && perName.has(word) && objectReadingOf(word) === 'decoration'))]);
|
|
206
|
+
}
|
|
182
207
|
continue;
|
|
183
208
|
}
|
|
184
209
|
const templates = [...slot.functions.values()]
|
|
185
210
|
.sort((a, b) => codeUnitCompare(JSON.stringify(a), JSON.stringify(b)));
|
|
186
211
|
if (new Set(templates.map(template => template.params.length)).size !== 1) continue;
|
|
212
|
+
if (templates.every(template => template.body && endsFunctionHead(template.body)) &&
|
|
213
|
+
!templates.some(template => leadsWithProjectMacro(template.body, perName))) {
|
|
214
|
+
functionHeads.push([name, templates]);
|
|
215
|
+
}
|
|
187
216
|
// Only an attribute or return-type macro (its expansion reads as
|
|
188
217
|
// specifiers or a type) can explain a misread declaration; statement
|
|
189
218
|
// macros such as test assertions read as calls.
|
|
@@ -197,8 +226,8 @@ function projectMacroDictionary(index, files) {
|
|
|
197
226
|
if (reading === 'decoration' || reading === 'type') functionMacros.push([name, templates]);
|
|
198
227
|
}
|
|
199
228
|
const byLanguage = {};
|
|
200
|
-
if (objectBodies.length > 0 || functionMacros.length > 0) {
|
|
201
|
-
const entries = { objectBodies, functionMacros };
|
|
229
|
+
if (objectBodies.length > 0 || functionMacros.length > 0 || functionHeads.length > 0) {
|
|
230
|
+
const entries = { objectBodies, functionMacros, functionHeads };
|
|
202
231
|
byLanguage.c = entries;
|
|
203
232
|
byLanguage.cpp = entries;
|
|
204
233
|
}
|
|
@@ -222,6 +251,7 @@ function contextFor(dictionary, language, excluded = null) {
|
|
|
222
251
|
key: `${dictionary.key}-${[...excluded].sort(codeUnitCompare).join(',')}`,
|
|
223
252
|
objectBodies: new Map(entry.objectBodies.filter(keep)),
|
|
224
253
|
functionMacros: new Map(entry.functionMacros.filter(keep)),
|
|
254
|
+
functionHeads: new Map((entry.functionHeads || []).filter(keep)),
|
|
225
255
|
consulted: new Set(),
|
|
226
256
|
};
|
|
227
257
|
}
|
|
@@ -233,7 +263,8 @@ function contextFor(dictionary, language, excluded = null) {
|
|
|
233
263
|
}
|
|
234
264
|
let maps = byLanguage.get(language);
|
|
235
265
|
if (!maps) {
|
|
236
|
-
maps = { objectBodies: new Map(entry.objectBodies), functionMacros: new Map(entry.functionMacros)
|
|
266
|
+
maps = { objectBodies: new Map(entry.objectBodies), functionMacros: new Map(entry.functionMacros),
|
|
267
|
+
functionHeads: new Map(entry.functionHeads || []) };
|
|
237
268
|
byLanguage.set(language, maps);
|
|
238
269
|
}
|
|
239
270
|
return { key: dictionary.key, ...maps, consulted: new Set() };
|
|
@@ -245,8 +276,9 @@ function keyOfNames(dictionary, language, names) {
|
|
|
245
276
|
const entry = dictionary?.byLanguage?.[language];
|
|
246
277
|
const objects = new Map(entry?.objectBodies || []);
|
|
247
278
|
const functions = new Map(entry?.functionMacros || []);
|
|
279
|
+
const heads = new Map(entry?.functionHeads || []);
|
|
248
280
|
const parts = [...new Set(names)].sort(codeUnitCompare)
|
|
249
|
-
.map(name => [name, objects.get(name) || null, functions.get(name) || null]);
|
|
281
|
+
.map(name => [name, objects.get(name) || null, functions.get(name) || null, heads.get(name) || null]);
|
|
250
282
|
return crypto.createHash('sha1').update(JSON.stringify(parts)).digest('base64url');
|
|
251
283
|
}
|
|
252
284
|
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* core/go-packages.js - Go package identity and build configurations.
|
|
3
|
+
*
|
|
4
|
+
* A Go package is the set of files of one directory that share a package
|
|
5
|
+
* clause: `package x` files (test files included, for `go test`) and the
|
|
6
|
+
* external test package `package x_test` of the same directory are two
|
|
7
|
+
* packages, and a non-test file never sees a `_test.go` declaration. One
|
|
8
|
+
* package-level identifier denotes one entity in a build configuration; files
|
|
9
|
+
* that declare it under different `//go:build` constraints (or GOOS/GOARCH
|
|
10
|
+
* file-name suffixes) are configuration alternatives of one item (fix #425).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
'use strict';
|
|
14
|
+
|
|
15
|
+
const path = require('path');
|
|
16
|
+
|
|
17
|
+
const GO_OS = new Set(['aix', 'android', 'darwin', 'dragonfly', 'freebsd', 'hurd', 'illumos', 'ios', 'js',
|
|
18
|
+
'linux', 'nacl', 'netbsd', 'openbsd', 'plan9', 'solaris', 'wasip1', 'windows', 'zos']);
|
|
19
|
+
const GO_ARCH = new Set(['386', 'amd64', 'amd64p32', 'arm', 'arm64', 'arm64be', 'armbe', 'loong64', 'mips',
|
|
20
|
+
'mips64', 'mips64le', 'mips64p32', 'mips64p32le', 'mipsle', 'ppc', 'ppc64', 'ppc64le', 'riscv', 'riscv64',
|
|
21
|
+
's390', 's390x', 'sparc', 'sparc64', 'wasm']);
|
|
22
|
+
|
|
23
|
+
// Package-level type kinds a method set belongs to.
|
|
24
|
+
const GO_TYPE_KINDS = new Set(['struct', 'interface', 'type', 'class', 'enum']);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* The build configurations a Go file belongs to (fix #399): its
|
|
28
|
+
* `//go:build` expression plus the GOOS/GOARCH its file name implies
|
|
29
|
+
* (`x_linux.go`, `x_windows_amd64_test.go`); '' when every build compiles it.
|
|
30
|
+
*/
|
|
31
|
+
function goFileConstraint(index, file) {
|
|
32
|
+
const memo = index._opMemo?.('goFileConstraint', () => new Map());
|
|
33
|
+
const cached = memo?.get(file);
|
|
34
|
+
if (cached !== undefined) return cached;
|
|
35
|
+
const constraint = goFileConstraintUncached(index, file);
|
|
36
|
+
memo?.set(file, constraint);
|
|
37
|
+
return constraint;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function goFileConstraintUncached(index, file) {
|
|
41
|
+
const tag = index.files.get(file)?.buildConstraint || '';
|
|
42
|
+
const parts = path.basename(file, '.go').replace(/_test$/, '').split('_');
|
|
43
|
+
let implied = '';
|
|
44
|
+
if (parts.length >= 3 && GO_OS.has(parts[parts.length - 2]) && GO_ARCH.has(parts[parts.length - 1])) {
|
|
45
|
+
implied = `${parts[parts.length - 2]}/${parts[parts.length - 1]}`;
|
|
46
|
+
} else if (parts.length >= 2 && (GO_OS.has(parts[parts.length - 1]) || GO_ARCH.has(parts[parts.length - 1]))) {
|
|
47
|
+
implied = parts[parts.length - 1];
|
|
48
|
+
}
|
|
49
|
+
return tag || implied ? `${tag}|${implied}` : '';
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const isTestFile = file => file.endsWith('_test.go');
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Does a package-level declaration of `declFile` belong to the package a
|
|
56
|
+
* bare name in `siteFile` is resolved in? Same directory, same package
|
|
57
|
+
* clause (`x` and `x_test` are two packages), and a `_test.go` declaration
|
|
58
|
+
* only for a test file. A file whose clause is unknown keeps the directory
|
|
59
|
+
* rule.
|
|
60
|
+
*/
|
|
61
|
+
function goSamePackage(index, siteFile, declFile) {
|
|
62
|
+
if (!siteFile || !declFile) return false;
|
|
63
|
+
if (siteFile === declFile) return true;
|
|
64
|
+
if (path.dirname(siteFile) !== path.dirname(declFile)) return false;
|
|
65
|
+
const site = index.files.get(siteFile)?.packageName;
|
|
66
|
+
const decl = index.files.get(declFile)?.packageName;
|
|
67
|
+
if (site && decl && site !== decl) return false;
|
|
68
|
+
return isTestFile(siteFile) || !isTestFile(declFile);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The directories of the project packages `siteFile` dot-imports
|
|
73
|
+
* (`import . "p"`), whose exported package-level names its file block holds.
|
|
74
|
+
*/
|
|
75
|
+
function goDotImportDirs(index, siteFile) {
|
|
76
|
+
const memo = index._opMemo?.('goDotImportDirs', () => new Map());
|
|
77
|
+
if (memo?.has(siteFile)) return memo.get(siteFile);
|
|
78
|
+
const entry = index.files.get(siteFile);
|
|
79
|
+
let dirs = null;
|
|
80
|
+
for (const detail of entry?.importDetails || []) {
|
|
81
|
+
if (detail.type !== 'dot-import' || !detail.module) continue;
|
|
82
|
+
const rel = entry.moduleResolved?.[detail.module];
|
|
83
|
+
if (!rel) continue;
|
|
84
|
+
const abs = path.isAbsolute(rel) ? rel : path.join(index.root, rel);
|
|
85
|
+
(dirs || (dirs = new Set())).add(path.dirname(abs));
|
|
86
|
+
}
|
|
87
|
+
memo?.set(siteFile, dirs);
|
|
88
|
+
return dirs;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Does a bare name in `siteFile` reach the package-level declaration `def`?
|
|
93
|
+
* Its own package's declarations (goSamePackage), or an exported non-test
|
|
94
|
+
* declaration of a package the file dot-imports (Go spec, Import
|
|
95
|
+
* declarations: the external test package `x_test` that dot-imports `x` sees
|
|
96
|
+
* x's exported names unqualified).
|
|
97
|
+
*/
|
|
98
|
+
function goBareNameReaches(index, siteFile, def) {
|
|
99
|
+
if (!def?.file) return false;
|
|
100
|
+
if (goSamePackage(index, siteFile, def.file)) return true;
|
|
101
|
+
if (isTestFile(def.file) || !/^\p{Lu}/u.test(def.name || '')) return false;
|
|
102
|
+
const dirs = goDotImportDirs(index, siteFile);
|
|
103
|
+
if (!dirs || !dirs.has(path.dirname(def.file))) return false;
|
|
104
|
+
// The imported package is the directory's non-test package.
|
|
105
|
+
return !String(index.files.get(def.file)?.packageName || '').endsWith('_test');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Are two declarations configuration alternatives of one Go item? Files of
|
|
110
|
+
* one package that both declare a package-level name compile together only
|
|
111
|
+
* in a program that does not build (the name would be redeclared), so two
|
|
112
|
+
* such declarations under different file constraints are one item in two
|
|
113
|
+
* build configurations: same name, same receiver type, one package, one
|
|
114
|
+
* test side, and at least one constrained file.
|
|
115
|
+
*/
|
|
116
|
+
function goBuildAlternatives(index, a, b) {
|
|
117
|
+
if (!a || !b || a === b || a.file === b.file || a.name !== b.name) return false;
|
|
118
|
+
if (path.dirname(a.file) !== path.dirname(b.file)) return false;
|
|
119
|
+
if (isTestFile(a.file) !== isTestFile(b.file)) return false;
|
|
120
|
+
const pa = index.files.get(a.file)?.packageName;
|
|
121
|
+
const pb = index.files.get(b.file)?.packageName;
|
|
122
|
+
if (!pa || pa !== pb) return false;
|
|
123
|
+
if (a.lexicalScopeStartLine != null || b.lexicalScopeStartLine != null) return false;
|
|
124
|
+
if (a.isNested || b.isNested) return false;
|
|
125
|
+
const ownerOf = d => String(d.receiver || d.className || '').replace(/^\*/, '').replace(/\[.*$/, '') || null;
|
|
126
|
+
if (ownerOf(a) !== ownerOf(b)) return false;
|
|
127
|
+
// A function and a type of one name are not alternatives of one item.
|
|
128
|
+
if (GO_TYPE_KINDS.has(a.type) !== GO_TYPE_KINDS.has(b.type)) return false;
|
|
129
|
+
const ca = goFileConstraint(index, a.file);
|
|
130
|
+
const cb = goFileConstraint(index, b.file);
|
|
131
|
+
return ca !== cb && !!(ca || cb);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The alternatives of one item a site in `siteFile` reaches: a site in a
|
|
136
|
+
* file whose constraint is one alternative's own compiles only with that
|
|
137
|
+
* alternative (the site's file and that alternative's file build together,
|
|
138
|
+
* so no other alternative does); any other site reaches each of them.
|
|
139
|
+
*/
|
|
140
|
+
function goAlternativesAt(index, siteFile, alternatives) {
|
|
141
|
+
if (!siteFile || alternatives.length < 2) return alternatives;
|
|
142
|
+
const own = alternatives.filter(d => d.file === siteFile);
|
|
143
|
+
if (own.length > 0) return own;
|
|
144
|
+
const site = goFileConstraint(index, siteFile);
|
|
145
|
+
if (!site) return alternatives;
|
|
146
|
+
const same = alternatives.filter(d => goFileConstraint(index, d.file) === site);
|
|
147
|
+
return same.length > 0 ? same : alternatives;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Package-level types named `name` the package of `siteFile` declares
|
|
152
|
+
* (fix #425): what a receiver no local binds (`T.M`, `(*T).M`) denotes when
|
|
153
|
+
* the package declares a type of that name.
|
|
154
|
+
*/
|
|
155
|
+
function goPackageTypes(index, siteFile, name) {
|
|
156
|
+
if (!name) return [];
|
|
157
|
+
return (index.symbols.get(name) || []).filter(d => d.file && GO_TYPE_KINDS.has(d.type) &&
|
|
158
|
+
d.lexicalScopeStartLine == null && !d.className && !d.receiver &&
|
|
159
|
+
index.files.get(d.file)?.language === 'go' && goSamePackage(index, siteFile, d.file));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
module.exports = {
|
|
163
|
+
goFileConstraint,
|
|
164
|
+
goSamePackage,
|
|
165
|
+
goDotImportDirs,
|
|
166
|
+
goBareNameReaches,
|
|
167
|
+
goBuildAlternatives,
|
|
168
|
+
goAlternativesAt,
|
|
169
|
+
goPackageTypes,
|
|
170
|
+
GO_TYPE_KINDS,
|
|
171
|
+
};
|
package/core/graph-build.js
CHANGED
|
@@ -604,6 +604,8 @@ function _applyGoImportNames(index, filePath, fileEntry, moduleResolved, dirToGo
|
|
|
604
604
|
* Build inheritance relationship graphs
|
|
605
605
|
*/
|
|
606
606
|
function buildInheritanceGraph(index) {
|
|
607
|
+
index._implicitConversionOwners = null;
|
|
608
|
+
index._constructionAliases = null;
|
|
607
609
|
index.extendsGraph.clear();
|
|
608
610
|
index.extendedByGraph.clear();
|
|
609
611
|
|