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
package/languages/c-family.js
CHANGED
|
@@ -15,6 +15,7 @@ const {
|
|
|
15
15
|
traverseTree,
|
|
16
16
|
nodeTextWithoutComments,
|
|
17
17
|
traverseTreeCached,
|
|
18
|
+
getCachedNodeList,
|
|
18
19
|
nodeToLocation,
|
|
19
20
|
extractJSDocstring,
|
|
20
21
|
visitNameNodes,
|
|
@@ -403,6 +404,18 @@ const MAX_TYPE_EVIDENCE_OCCURRENCES = 512;
|
|
|
403
404
|
const definitionReadingMemo = new Map(); // grammar + replacement list -> reading (fix #387)
|
|
404
405
|
const IDENTIFIER_TEXT = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
405
406
|
|
|
407
|
+
/**
|
|
408
|
+
* A replacement list without the object-like macro names `isDecoration`
|
|
409
|
+
* accepts (names that expand to declaration specifiers or nothing), as
|
|
410
|
+
* preprocessing tokens joined by spaces (fix #427).
|
|
411
|
+
*/
|
|
412
|
+
function withoutDecorationMacros(body, isDecoration) {
|
|
413
|
+
const tokens = lexPP(String(body || ''));
|
|
414
|
+
if (!tokens.some(token => token.k === 'id' && isDecoration(token.v))) return body;
|
|
415
|
+
return tokens.filter((token, i) => !(token.k === 'id' && tokens[i + 1]?.v !== '(' && isDecoration(token.v)))
|
|
416
|
+
.map(token => token.v).join(' ');
|
|
417
|
+
}
|
|
418
|
+
|
|
406
419
|
/**
|
|
407
420
|
* Per-file evidence for decoration decisions, gathered lazily from the
|
|
408
421
|
* file's own tree and directives: names proven types by an occurrence, names
|
|
@@ -445,6 +458,9 @@ class DecorationEvidence {
|
|
|
445
458
|
}
|
|
446
459
|
}
|
|
447
460
|
if (this.defines.has(name)) return this.defines.get(name);
|
|
461
|
+
// A definition that names itself (or one that names it) reads as
|
|
462
|
+
// no reading while it is being read.
|
|
463
|
+
this.defines.set(name, null);
|
|
448
464
|
const bodies = [];
|
|
449
465
|
const code = this.code;
|
|
450
466
|
// A name the file does not define reads by the project's definitions
|
|
@@ -470,6 +486,14 @@ class DecorationEvidence {
|
|
|
470
486
|
}
|
|
471
487
|
bodies.push(body.replace(/\/\*[\s\S]*?\*\/|\/\/.*$/gm, ' '));
|
|
472
488
|
}
|
|
489
|
+
// Object-like macros a body names that themselves read as
|
|
490
|
+
// decoration expand to specifiers or nothing: the body reads as the
|
|
491
|
+
// rest of it (fix #427: `#define API __declspec(dllexport) VIS` with
|
|
492
|
+
// `#define VIS __attribute__((visibility("default")))` or empty).
|
|
493
|
+
const reduced = bodies.map(body => withoutDecorationMacros(body, word => word !== name &&
|
|
494
|
+
(this.defineEnds.has(word) || externalMacroContext?.objectBodies?.has(word)) &&
|
|
495
|
+
this.defined(word) === 'decoration'));
|
|
496
|
+
bodies.splice(0, bodies.length, ...reduced);
|
|
473
497
|
const readings = new Set(bodies.map(body => this.readBody(body)));
|
|
474
498
|
let reading = readings.size === 1 ? [...readings][0] : null;
|
|
475
499
|
// A statement macro defined empty in some configuration
|
|
@@ -503,11 +527,24 @@ class DecorationEvidence {
|
|
|
503
527
|
}
|
|
504
528
|
|
|
505
529
|
probeBody(text) {
|
|
506
|
-
|
|
507
|
-
const tree = safeParse(this.parser,
|
|
530
|
+
const statementProbe = source => {
|
|
531
|
+
const tree = safeParse(this.parser, source, undefined, PARSE_OPTIONS);
|
|
508
532
|
const clean = !!tree && !tree.rootNode.hasError;
|
|
509
533
|
tree?.delete?.();
|
|
510
|
-
return clean
|
|
534
|
+
return clean;
|
|
535
|
+
};
|
|
536
|
+
if (/^catch\b/.test(text)) {
|
|
537
|
+
// A complete handler, or (fix #427) a handler head the block
|
|
538
|
+
// after the invocation completes (`catch(...)`).
|
|
539
|
+
return statementProbe(`void __ucn_probe__() { try { } ${text} }`) ||
|
|
540
|
+
statementProbe(`void __ucn_probe__() { try { } ${text} { } }`) ? 'statement' : null;
|
|
541
|
+
}
|
|
542
|
+
// A conditional head with its condition (`if(true)`, `if(false)`):
|
|
543
|
+
// removed, the block after it stays a statement (fix #427: one
|
|
544
|
+
// configuration's `try`/`catch(...)` is another's `if(true)` /
|
|
545
|
+
// `if(false)`).
|
|
546
|
+
if (/^(?:if|while|for|switch)\s*\(/.test(text)) {
|
|
547
|
+
return statementProbe(`void __ucn_probe__() { ${text} { } }`) ? 'statement' : null;
|
|
511
548
|
}
|
|
512
549
|
const probe = source => {
|
|
513
550
|
const tree = safeParse(this.parser, source, undefined, PARSE_OPTIONS);
|
|
@@ -997,6 +1034,14 @@ function dependentTypeInBodyError(node) {
|
|
|
997
1034
|
return false;
|
|
998
1035
|
}
|
|
999
1036
|
|
|
1037
|
+
/** `Class::*` with no declarator name after the `*` (the grammar's MISSING name). */
|
|
1038
|
+
function memberPointerAbstractDeclarator(node) {
|
|
1039
|
+
const name = node.childForFieldName('name');
|
|
1040
|
+
if (name?.type !== 'pointer_type_declarator') return false;
|
|
1041
|
+
const declarator = name.childForFieldName('declarator');
|
|
1042
|
+
return !!declarator?.isMissing && name.namedChildCount === 1;
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1000
1045
|
function macroTypeRanges(tree, code, evidence = null) {
|
|
1001
1046
|
const ranges = [];
|
|
1002
1047
|
const pairs = [];
|
|
@@ -1043,6 +1088,15 @@ function macroTypeRanges(tree, code, evidence = null) {
|
|
|
1043
1088
|
return true;
|
|
1044
1089
|
};
|
|
1045
1090
|
traverseTree(tree.rootNode, node => {
|
|
1091
|
+
// An abstract pointer-to-member declarator (`Ret (Class::*)(Args...)`,
|
|
1092
|
+
// `Type Class::*`) has no reading in the grammar, which then misreads
|
|
1093
|
+
// the whole declaration (fix #427). Read with `*` in place of
|
|
1094
|
+
// `Class::*`, it is the pointer declarator of the same shape; the
|
|
1095
|
+
// parameter keeps its written type (structuredParams).
|
|
1096
|
+
if (treeHasError && node.type === 'qualified_identifier' && memberPointerAbstractDeclarator(node)) {
|
|
1097
|
+
ranges.push([node.startIndex, node.endIndex, '*'.padStart(node.endIndex - node.startIndex, ' ')]);
|
|
1098
|
+
return false;
|
|
1099
|
+
}
|
|
1046
1100
|
ranges.push(...errorMacroRanges(node, code, pairs, evidence));
|
|
1047
1101
|
const statementMacro = statementMacroRange(node, code, evidence);
|
|
1048
1102
|
if (statementMacro) ranges.push(statementMacro);
|
|
@@ -1575,6 +1629,12 @@ function preprocessorDirective(line) {
|
|
|
1575
1629
|
*/
|
|
1576
1630
|
const CONDITIONAL_RECOVERY_MAX_BYTES = 256 * 1024;
|
|
1577
1631
|
|
|
1632
|
+
// Keep every branch's tokens while removing only conditional directives.
|
|
1633
|
+
// Consumers must still parse the combined tokens to establish their meaning.
|
|
1634
|
+
function conditionalHeaderText(code) {
|
|
1635
|
+
return code.split(/(?<=\n)/).map(line => preprocessorDirective(line) ? blankLine(line) : line).join('');
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1578
1638
|
function conditionalRecoverySources(code) {
|
|
1579
1639
|
if (Buffer.byteLength(code) > CONDITIONAL_RECOVERY_MAX_BYTES) return [];
|
|
1580
1640
|
const lines = code.match(/.*(?:\r\n|\n|\r|$)/g)?.filter(Boolean) || [];
|
|
@@ -1675,6 +1735,22 @@ function conditionalRecoverySources(code) {
|
|
|
1675
1735
|
const source = selected.join('');
|
|
1676
1736
|
if (!sources.includes(source)) sources.push(source);
|
|
1677
1737
|
}
|
|
1738
|
+
// Every alternative head of one construct at once (fix #430).
|
|
1739
|
+
const directiveRows = [];
|
|
1740
|
+
const directiveEnd = new Map();
|
|
1741
|
+
for (let row = 0; row < lines.length; row++) {
|
|
1742
|
+
if (!directives[row]) continue;
|
|
1743
|
+
let end = row;
|
|
1744
|
+
while (end + 1 < lines.length && /\\[ \t]*\r?\n?$/.test(lines[end])) end++;
|
|
1745
|
+
directiveEnd.set(row, end);
|
|
1746
|
+
directiveRows.push({ row, kind: directives[row].kind });
|
|
1747
|
+
}
|
|
1748
|
+
const closures = alternativeBranchClosures(lines, directiveRows, directiveEnd);
|
|
1749
|
+
if (closures) {
|
|
1750
|
+
const source = lines.map((line, row) => closures.replace.has(row) ? closures.replace.get(row)
|
|
1751
|
+
: closures.rows.has(row) ? blankLine(line) : line).join('');
|
|
1752
|
+
if (!sources.includes(source)) sources.push(source);
|
|
1753
|
+
}
|
|
1678
1754
|
return sources;
|
|
1679
1755
|
}
|
|
1680
1756
|
|
|
@@ -1683,6 +1759,77 @@ const DIRECTIVE_TOKEN_TYPES = new Set([
|
|
|
1683
1759
|
'#if', '#ifdef', '#ifndef', '#elif', '#elifdef', '#elifndef', '#else', '#endif',
|
|
1684
1760
|
]);
|
|
1685
1761
|
|
|
1762
|
+
/**
|
|
1763
|
+
* The `{` minus `}` count of the program tokens on rows [from, to): directive
|
|
1764
|
+
* lines (a `#define` replacement list, with its continuation lines) are not
|
|
1765
|
+
* program tokens, nor are braces in comments and literals.
|
|
1766
|
+
*/
|
|
1767
|
+
function programBraceBalance(lines, from, to) {
|
|
1768
|
+
const kept = [];
|
|
1769
|
+
for (let row = from; row < to; row++) {
|
|
1770
|
+
if (/^\s*#/.test(lines[row])) {
|
|
1771
|
+
while (row + 1 < to && /\\[ \t]*\r?\n?$/.test(lines[row])) row++;
|
|
1772
|
+
continue;
|
|
1773
|
+
}
|
|
1774
|
+
kept.push(lines[row]);
|
|
1775
|
+
}
|
|
1776
|
+
const text = kept.join('');
|
|
1777
|
+
let balance = 0;
|
|
1778
|
+
for (let index = 0; index < text.length; index++) {
|
|
1779
|
+
const skipped = skipLexicalRegion(text, index);
|
|
1780
|
+
if (skipped != null) {
|
|
1781
|
+
index = skipped - 1;
|
|
1782
|
+
continue;
|
|
1783
|
+
}
|
|
1784
|
+
if (text[index] === '{') balance++;
|
|
1785
|
+
else if (text[index] === '}') balance--;
|
|
1786
|
+
}
|
|
1787
|
+
return balance;
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1790
|
+
/**
|
|
1791
|
+
* Alternative heads of one construct (fix #430): a conditional whose every
|
|
1792
|
+
* branch opens the same number k of braces the code after it closes
|
|
1793
|
+
* (`#if X T f() { #else T f() { #endif ... }`, entt `type_hash::value`).
|
|
1794
|
+
* Each branch is read as its own construct, closed by k braces written into
|
|
1795
|
+
* the directive line that ends it (the `#elif`/`#else` line, blank in every
|
|
1796
|
+
* configuration), so every alternative is a declaration; directive lines are
|
|
1797
|
+
* blanked and every other byte and row kept. Returns { rows, replace } for
|
|
1798
|
+
* the groups of `directives` (`{ row, kind }` in order, `directiveEnd` maps a
|
|
1799
|
+
* row to its last continuation row) that qualify, or null.
|
|
1800
|
+
*/
|
|
1801
|
+
function alternativeBranchClosures(lines, directives, directiveEnd) {
|
|
1802
|
+
const rows = new Set();
|
|
1803
|
+
const replace = new Map();
|
|
1804
|
+
const stack = [];
|
|
1805
|
+
for (const directive of directives) {
|
|
1806
|
+
if (directive.kind === 'if' || directive.kind === 'ifdef' || directive.kind === 'ifndef') {
|
|
1807
|
+
stack.push({ branches: [directive.row] });
|
|
1808
|
+
} else if (directive.kind === 'elif' || directive.kind === 'else') {
|
|
1809
|
+
stack[stack.length - 1]?.branches.push(directive.row);
|
|
1810
|
+
} else if (directive.kind === 'endif') {
|
|
1811
|
+
const group = stack.pop();
|
|
1812
|
+
if (!group) return null;
|
|
1813
|
+
if (group.branches.length < 2) continue;
|
|
1814
|
+
const edges = [...group.branches, directive.row];
|
|
1815
|
+
const balances = group.branches.map((row, b) => programBraceBalance(lines, row + 1, edges[b + 1]));
|
|
1816
|
+
const k = balances[0];
|
|
1817
|
+
if (!(k > 0) || balances.some(balance => balance !== k)) continue;
|
|
1818
|
+
const closers = group.branches.slice(1);
|
|
1819
|
+
if (closers.some(row => directiveEnd.get(row) !== row ||
|
|
1820
|
+
lines[row].replace(/[\r\n]+$/, '').length < k)) continue;
|
|
1821
|
+
for (const row of edges) {
|
|
1822
|
+
for (let line = row; line <= directiveEnd.get(row); line++) rows.add(line);
|
|
1823
|
+
}
|
|
1824
|
+
for (const row of closers) {
|
|
1825
|
+
const blank = blankLine(lines[row]);
|
|
1826
|
+
replace.set(row, '}'.repeat(k) + blank.slice(k));
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
return rows.size > 0 ? { rows, replace } : null;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1686
1833
|
/**
|
|
1687
1834
|
* fix #391: the source with the preprocessor conditionals the grammar could
|
|
1688
1835
|
* not place resolved, or null. tree-sitter models a conditional as a node
|
|
@@ -1697,7 +1844,7 @@ const DIRECTIVE_TOKEN_TYPES = new Set([
|
|
|
1697
1844
|
* number. Only the conditionals the grammar could not place change; every
|
|
1698
1845
|
* other line reads as before.
|
|
1699
1846
|
*/
|
|
1700
|
-
function misplacedDirectiveRepair(tree, source) {
|
|
1847
|
+
function misplacedDirectiveRepair(tree, source, info = null) {
|
|
1701
1848
|
const misplaced = new Set();
|
|
1702
1849
|
const visit = node => {
|
|
1703
1850
|
const error = node.type === 'ERROR';
|
|
@@ -1712,30 +1859,7 @@ function misplacedDirectiveRepair(tree, source) {
|
|
|
1712
1859
|
// conditionals (`#ifdef X else { #endif ... #ifdef X } #endif`, fix
|
|
1713
1860
|
// #393): the grammar reads it as a block that swallows the declarations
|
|
1714
1861
|
// after it, with no ERROR near the directives.
|
|
1715
|
-
const braceBalance = (from, to) =>
|
|
1716
|
-
// Program tokens only: directive lines (a `#define` replacement
|
|
1717
|
-
// list, with its continuation lines) are not.
|
|
1718
|
-
const kept = [];
|
|
1719
|
-
for (let row = from; row < to; row++) {
|
|
1720
|
-
if (/^\s*#/.test(lines[row])) {
|
|
1721
|
-
while (row + 1 < to && /\\[ \t]*\r?\n?$/.test(lines[row])) row++;
|
|
1722
|
-
continue;
|
|
1723
|
-
}
|
|
1724
|
-
kept.push(lines[row]);
|
|
1725
|
-
}
|
|
1726
|
-
const text = kept.join('');
|
|
1727
|
-
let balance = 0;
|
|
1728
|
-
for (let index = 0; index < text.length; index++) {
|
|
1729
|
-
const skipped = skipLexicalRegion(text, index);
|
|
1730
|
-
if (skipped != null) {
|
|
1731
|
-
index = skipped - 1;
|
|
1732
|
-
continue;
|
|
1733
|
-
}
|
|
1734
|
-
if (text[index] === '{') balance++;
|
|
1735
|
-
else if (text[index] === '}') balance--;
|
|
1736
|
-
}
|
|
1737
|
-
return balance;
|
|
1738
|
-
};
|
|
1862
|
+
const braceBalance = (from, to) => programBraceBalance(lines, from, to);
|
|
1739
1863
|
// Directive line spans (a directive continues over backslash-newlines).
|
|
1740
1864
|
const directiveEnd = new Map();
|
|
1741
1865
|
const directives = [];
|
|
@@ -1748,6 +1872,8 @@ function misplacedDirectiveRepair(tree, source) {
|
|
|
1748
1872
|
directives.push({ row, kind: directive.kind });
|
|
1749
1873
|
}
|
|
1750
1874
|
const blank = new Set();
|
|
1875
|
+
// Groups of alternative heads keep every branch, each closed in place.
|
|
1876
|
+
const closures = alternativeBranchClosures(lines, directives, directiveEnd);
|
|
1751
1877
|
const stack = [];
|
|
1752
1878
|
for (const directive of directives) {
|
|
1753
1879
|
if (directive.kind === 'if' || directive.kind === 'ifdef' || directive.kind === 'ifndef') {
|
|
@@ -1758,6 +1884,7 @@ function misplacedDirectiveRepair(tree, source) {
|
|
|
1758
1884
|
const group = stack.pop();
|
|
1759
1885
|
if (!group) return null;
|
|
1760
1886
|
const rows = [...group.branches, directive.row];
|
|
1887
|
+
if (closures?.rows.has(directive.row)) continue;
|
|
1761
1888
|
if (!rows.some(row => misplaced.has(row))) {
|
|
1762
1889
|
const edges = [...group.branches, directive.row];
|
|
1763
1890
|
let split = false;
|
|
@@ -1792,8 +1919,10 @@ function misplacedDirectiveRepair(tree, source) {
|
|
|
1792
1919
|
}
|
|
1793
1920
|
}
|
|
1794
1921
|
}
|
|
1795
|
-
if (stack.length > 0 || blank.size === 0) return null;
|
|
1796
|
-
|
|
1922
|
+
if (stack.length > 0 || (blank.size === 0 && !closures)) return null;
|
|
1923
|
+
if (info && closures) info.closedAlternatives = true;
|
|
1924
|
+
return lines.map((line, row) => closures?.replace.has(row) ? closures.replace.get(row)
|
|
1925
|
+
: blank.has(row) || closures?.rows.has(row) ? blankLine(line) : line).join('');
|
|
1797
1926
|
}
|
|
1798
1927
|
|
|
1799
1928
|
const STRUCTURE_SCORE_TYPES = [
|
|
@@ -1928,8 +2057,9 @@ const KNOWN_MACRO_ROUNDS = 3;
|
|
|
1928
2057
|
* statements again), so a few rounds run. Returns { source, tree,
|
|
1929
2058
|
* invocations }; `blank` stays set only on invocations the source blanks.
|
|
1930
2059
|
*/
|
|
1931
|
-
function knownMacroInvocationRecovery(parser, source) {
|
|
2060
|
+
function knownMacroInvocationRecovery(parser, source, rawCapture = null) {
|
|
1932
2061
|
let tree = safeParse(parser, source, undefined, PARSE_OPTIONS);
|
|
2062
|
+
if (rawCapture?.source === source) rawCapture.tree = tree;
|
|
1933
2063
|
const own = directiveFunctionMacros(source);
|
|
1934
2064
|
// The project's attribute and return-type macros matter only where the
|
|
1935
2065
|
// grammar failed (fix #396).
|
|
@@ -1961,7 +2091,7 @@ function knownMacroInvocationRecovery(parser, source) {
|
|
|
1961
2091
|
for (const invocation of blanks) kept.set(invocation.start, { ...invocation, blank: null });
|
|
1962
2092
|
break;
|
|
1963
2093
|
}
|
|
1964
|
-
tree.delete?.();
|
|
2094
|
+
if (tree !== rawCapture?.tree) tree.delete?.();
|
|
1965
2095
|
tree = candidate;
|
|
1966
2096
|
current = blanked;
|
|
1967
2097
|
}
|
|
@@ -1991,7 +2121,7 @@ function withExternalFunctionMacros(macros) {
|
|
|
1991
2121
|
return { defs, undefs: merged.undefs };
|
|
1992
2122
|
}
|
|
1993
2123
|
|
|
1994
|
-
function parseTree(parser, code) {
|
|
2124
|
+
function parseTree(parser, code, rawCapture = null) {
|
|
1995
2125
|
const cacheKey = treeCacheKey(code);
|
|
1996
2126
|
const cached = cachedCFamilyTree(parser, cacheKey);
|
|
1997
2127
|
if (cached) return cached;
|
|
@@ -2016,7 +2146,7 @@ function parseTree(parser, code) {
|
|
|
2016
2146
|
// Invocations of the file's own function-like macros that the grammar
|
|
2017
2147
|
// read as declarations are the preprocessor's view before any other
|
|
2018
2148
|
// recovery (fix #385).
|
|
2019
|
-
const known = knownMacroInvocationRecovery(parser, pinnedSource);
|
|
2149
|
+
const known = knownMacroInvocationRecovery(parser, pinnedSource, rawCapture);
|
|
2020
2150
|
const source = known.source;
|
|
2021
2151
|
const tree = known.tree;
|
|
2022
2152
|
const knownSource = source !== pinnedSource ? source : null;
|
|
@@ -2064,7 +2194,7 @@ function parseTree(parser, code) {
|
|
|
2064
2194
|
const releaseTree = candidate => {
|
|
2065
2195
|
if (!candidate || !liveTrees.has(candidate)) return;
|
|
2066
2196
|
liveTrees.delete(candidate);
|
|
2067
|
-
candidate.delete?.();
|
|
2197
|
+
if (candidate !== rawCapture?.tree) candidate.delete?.();
|
|
2068
2198
|
};
|
|
2069
2199
|
// Misreads a damage hid (fix #396): once a configuration or a directive
|
|
2070
2200
|
// repair restores the structure around them, the decoration rules and
|
|
@@ -2221,7 +2351,8 @@ function parseTree(parser, code) {
|
|
|
2221
2351
|
// round over the repaired tree, which exposes misreads the damage hid.
|
|
2222
2352
|
if (!conditionalApplied && !(repairFolded && best) &&
|
|
2223
2353
|
(largeSource || attributeSelected.rootNode.hasError)) {
|
|
2224
|
-
const
|
|
2354
|
+
const repairInfo = {};
|
|
2355
|
+
const repairedSource = misplacedDirectiveRepair(attributeSelected, attributeSource, repairInfo);
|
|
2225
2356
|
if (repairedSource) {
|
|
2226
2357
|
let candidate = safeParse(parser, repairedSource, undefined, PARSE_OPTIONS);
|
|
2227
2358
|
liveTrees.add(candidate);
|
|
@@ -2236,7 +2367,12 @@ function parseTree(parser, code) {
|
|
|
2236
2367
|
candidateErrors = round.errors;
|
|
2237
2368
|
}
|
|
2238
2369
|
}
|
|
2239
|
-
|
|
2370
|
+
// A repair that reads alternative heads (fix #430) is also
|
|
2371
|
+
// kept at an equal error count when it recovers structure,
|
|
2372
|
+
// as the configuration sweep prefers the richest view.
|
|
2373
|
+
if (candidateErrors < selectedErrors || repairInfo.closedAlternatives &&
|
|
2374
|
+
candidateErrors === selectedErrors &&
|
|
2375
|
+
treeStructureScore(candidate) > treeStructureScore(attributeSelected)) {
|
|
2240
2376
|
best = candidate;
|
|
2241
2377
|
bestCode = candidateSource;
|
|
2242
2378
|
} else {
|
|
@@ -2502,6 +2638,15 @@ function withoutShadowedFreeFunctions(secondary, primaryClasses) {
|
|
|
2502
2638
|
});
|
|
2503
2639
|
}
|
|
2504
2640
|
|
|
2641
|
+
/** Whether a declarator names a qualified entity (`H<T>::x`, `*Foo::p`). */
|
|
2642
|
+
function qualifiedDeclarator(node) {
|
|
2643
|
+
for (let current = node, depth = 0; current && depth < 16; depth++) {
|
|
2644
|
+
if (current.type === 'qualified_identifier') return true;
|
|
2645
|
+
current = current.childForFieldName('declarator');
|
|
2646
|
+
}
|
|
2647
|
+
return false;
|
|
2648
|
+
}
|
|
2649
|
+
|
|
2505
2650
|
function unwrapDeclarator(node) {
|
|
2506
2651
|
let current = node;
|
|
2507
2652
|
const seen = new Set();
|
|
@@ -2539,6 +2684,9 @@ function functionDeclarator(node) {
|
|
|
2539
2684
|
}
|
|
2540
2685
|
const direct = node.childForFieldName('declarator');
|
|
2541
2686
|
if (direct?.type === 'operator_cast') return direct;
|
|
2687
|
+
// An out-of-class conversion operator (`T::operator int() const {..}`,
|
|
2688
|
+
// fix #427): the qualified name holds the operator_cast.
|
|
2689
|
+
if (direct?.type === 'qualified_identifier' && qualifiedOperatorCast(direct)) return direct;
|
|
2542
2690
|
const unwrapped = unwrapDeclarator(direct);
|
|
2543
2691
|
if (unwrapped) return unwrapped;
|
|
2544
2692
|
for (const child of node.namedChildren || []) {
|
|
@@ -2549,6 +2697,39 @@ function functionDeclarator(node) {
|
|
|
2549
2697
|
return null;
|
|
2550
2698
|
}
|
|
2551
2699
|
|
|
2700
|
+
/**
|
|
2701
|
+
* A C++17 deduction guide (`template<class T> W(T) -> W<T>;`, `explicit
|
|
2702
|
+
* W(const char*) -> W<int>;`): a declaration with no type whose function
|
|
2703
|
+
* declarator names a class template and whose trailing return type is a
|
|
2704
|
+
* template-id of that same template ([temp.deduct.guide]). It declares no
|
|
2705
|
+
* function; its name token names the class template (fix #427). Returns the
|
|
2706
|
+
* name node, or null.
|
|
2707
|
+
*/
|
|
2708
|
+
function deductionGuideOf(node) {
|
|
2709
|
+
if (!node || !['declaration', 'field_declaration', 'ERROR'].includes(node.type)) return null;
|
|
2710
|
+
if (node.childForFieldName?.('type')) return null;
|
|
2711
|
+
const declarator = node.type === 'ERROR'
|
|
2712
|
+
? (node.namedChildren || []).find(child => child.type === 'function_declarator')
|
|
2713
|
+
: node.childForFieldName('declarator');
|
|
2714
|
+
if (declarator?.type !== 'function_declarator') return null;
|
|
2715
|
+
const nameNode = declarator.childForFieldName('declarator');
|
|
2716
|
+
if (!nameNode || !['identifier', 'type_identifier', 'field_identifier'].includes(nameNode.type)) return null;
|
|
2717
|
+
const trailing = (declarator.namedChildren || []).find(child => child.type === 'trailing_return_type');
|
|
2718
|
+
let type = trailing?.namedChildren.find(child => child.type === 'type_descriptor')?.childForFieldName('type');
|
|
2719
|
+
while (type?.type === 'qualified_identifier') type = type.childForFieldName('name');
|
|
2720
|
+
if (type?.type !== 'template_type') return null;
|
|
2721
|
+
return type.childForFieldName('name')?.text === nameNode.text ? nameNode : null;
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
/** The operator_cast a qualified name ends in (`A::B::operator int`), or null. */
|
|
2725
|
+
function qualifiedOperatorCast(node) {
|
|
2726
|
+
let current = node;
|
|
2727
|
+
while (current?.type === 'qualified_identifier') {
|
|
2728
|
+
current = current.childForFieldName('name') || current.namedChildren[current.namedChildCount - 1];
|
|
2729
|
+
}
|
|
2730
|
+
return current?.type === 'operator_cast' ? current : null;
|
|
2731
|
+
}
|
|
2732
|
+
|
|
2552
2733
|
function canonicalCallableName(raw) {
|
|
2553
2734
|
const text = String(raw || '').trim();
|
|
2554
2735
|
if (!text.startsWith('operator')) return text;
|
|
@@ -2626,6 +2807,27 @@ function declaratorIdentity(declarator) {
|
|
|
2626
2807
|
const scopeNode = scopes[scopes.length - 1];
|
|
2627
2808
|
const qualifier = scopes.slice(0, -1).map(scope => scope.text.replace(/\s+/g, ''))
|
|
2628
2809
|
.filter(Boolean).join('::');
|
|
2810
|
+
if (nameNode?.type === 'operator_cast') {
|
|
2811
|
+
// `Text::operator Text::bool_type()` converts to the member type
|
|
2812
|
+
// the class body spells `bool_type` (fix #427): the owner's own
|
|
2813
|
+
// qualification is not part of the in-class name.
|
|
2814
|
+
const cast = declaratorIdentity(nameNode);
|
|
2815
|
+
if (!cast.name) return {};
|
|
2816
|
+
const owner = [...scopes.map(scope => scope.text.replace(/\s+/g, ''))].join('::');
|
|
2817
|
+
let name = cast.name;
|
|
2818
|
+
for (const prefix of [owner, scopeNode?.text?.replace(/\s+/g, '')].filter(Boolean)) {
|
|
2819
|
+
if (name.startsWith(`operator ${prefix}::`)) {
|
|
2820
|
+
name = `operator ${name.slice(`operator ${prefix}::`.length)}`;
|
|
2821
|
+
break;
|
|
2822
|
+
}
|
|
2823
|
+
}
|
|
2824
|
+
return {
|
|
2825
|
+
...cast,
|
|
2826
|
+
name,
|
|
2827
|
+
className: scopeNode?.text?.split('::').pop(),
|
|
2828
|
+
...(qualifier && { ownerQualifier: qualifier }),
|
|
2829
|
+
};
|
|
2830
|
+
}
|
|
2629
2831
|
return {
|
|
2630
2832
|
name: canonicalCallableName(nameNode?.text),
|
|
2631
2833
|
className: scopeNode?.text?.split('::').pop(),
|
|
@@ -3161,9 +3363,31 @@ function paramTypeText(param, identity) {
|
|
|
3161
3363
|
return typeText || null;
|
|
3162
3364
|
}
|
|
3163
3365
|
|
|
3164
|
-
|
|
3366
|
+
/**
|
|
3367
|
+
* The written text of a node the recovery read with `*` in place of an
|
|
3368
|
+
* abstract pointer-to-member declarator's `Class::*` (fix #427), or null.
|
|
3369
|
+
*/
|
|
3370
|
+
function writtenMemberPointerText(node, lines) {
|
|
3371
|
+
if (!node || !lines) return null;
|
|
3372
|
+
const start = node.startPosition;
|
|
3373
|
+
const end = node.endPosition;
|
|
3374
|
+
if (start.row !== end.row) return null;
|
|
3375
|
+
const written = lines[start.row]?.slice(start.column, end.column);
|
|
3376
|
+
return written && written !== node.text && /::\s*\*/.test(written)
|
|
3377
|
+
? written.replace(/\s+/g, ' ').trim() : null;
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3380
|
+
function paramsDisplayText(paramsNode, lines) {
|
|
3381
|
+
const text = writtenMemberPointerText(paramsNode, lines) || nodeTextWithoutComments(paramsNode);
|
|
3382
|
+
return text.replace(/^\(|\)$/g, '').trim();
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
function structuredParams(paramsNode, lines = null) {
|
|
3165
3386
|
if (!paramsNode) return [];
|
|
3166
3387
|
const result = [];
|
|
3388
|
+
// A parameter the recovery read as a pointer declarator in place of an
|
|
3389
|
+
// abstract pointer-to-member one keeps its written type (fix #427).
|
|
3390
|
+
const writtenMemberPointer = param => writtenMemberPointerText(param, lines);
|
|
3167
3391
|
for (const param of paramsNode.namedChildren || []) {
|
|
3168
3392
|
if (param.type !== 'parameter_declaration' &&
|
|
3169
3393
|
param.type !== 'optional_parameter_declaration' &&
|
|
@@ -3196,6 +3420,8 @@ function structuredParams(paramsNode) {
|
|
|
3196
3420
|
info.unnamed = true;
|
|
3197
3421
|
}
|
|
3198
3422
|
}
|
|
3423
|
+
const memberPointer = writtenMemberPointer(param);
|
|
3424
|
+
if (memberPointer && info.unnamed) info.name = memberPointer;
|
|
3199
3425
|
if (param.type === 'optional_parameter_declaration') info.optional = true;
|
|
3200
3426
|
let declaratorCursor = declarator;
|
|
3201
3427
|
let variadicDeclarator = false;
|
|
@@ -3387,25 +3613,47 @@ function templatedDeclaration(node) {
|
|
|
3387
3613
|
return current || node;
|
|
3388
3614
|
}
|
|
3389
3615
|
|
|
3616
|
+
/**
|
|
3617
|
+
* The cv- and ref-qualifiers of a member function's implicit object
|
|
3618
|
+
* parameter (`int f() const &`, fix #427): { memberConst, refQualifier }.
|
|
3619
|
+
*/
|
|
3620
|
+
function memberQualifiers(declarator) {
|
|
3621
|
+
let holder = declarator;
|
|
3622
|
+
if (holder?.type === 'qualified_identifier') holder = qualifiedOperatorCast(holder);
|
|
3623
|
+
if (holder?.type === 'operator_cast') {
|
|
3624
|
+
holder = holder.childForFieldName('declarator') ||
|
|
3625
|
+
(holder.namedChildren || []).find(child => child.type === 'abstract_function_declarator');
|
|
3626
|
+
}
|
|
3627
|
+
if (!holder) return {};
|
|
3628
|
+
const out = {};
|
|
3629
|
+
for (const child of holder.namedChildren || []) {
|
|
3630
|
+
if (child.type === 'type_qualifier' && child.text === 'const') out.memberConst = true;
|
|
3631
|
+
if (child.type === 'ref_qualifier') out.refQualifier = child.text.replace(/\s+/g, '');
|
|
3632
|
+
}
|
|
3633
|
+
return out;
|
|
3634
|
+
}
|
|
3635
|
+
|
|
3390
3636
|
function memberFromNode(node, className, access, lines, mode) {
|
|
3391
3637
|
const declarator = functionDeclarator(node);
|
|
3392
3638
|
if (!declarator) return null;
|
|
3393
3639
|
const identity = declaratorIdentity(declarator);
|
|
3394
3640
|
if (!identity.name) return null;
|
|
3395
3641
|
const paramsNode = parameterListOf(declarator);
|
|
3396
|
-
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
3642
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
|
|
3643
|
+
const ownerName = enclosingClass(node)?.name || className;
|
|
3397
3644
|
const isConstructor = mode === 'cpp' &&
|
|
3398
|
-
(identity.name ===
|
|
3645
|
+
(identity.name === ownerName || identity.name === `~${ownerName}`);
|
|
3399
3646
|
const modifiers = modifiersOf(node, access ? [access] : []);
|
|
3400
3647
|
if (isConstructor && identity.name.startsWith('~')) modifiers.push('destructor');
|
|
3401
3648
|
return {
|
|
3402
3649
|
name: identity.name,
|
|
3403
|
-
params: paramsNode ?
|
|
3404
|
-
paramsStructured: structuredParams(paramsNode),
|
|
3650
|
+
params: paramsNode ? paramsDisplayText(paramsNode, lines) : '...',
|
|
3651
|
+
paramsStructured: structuredParams(paramsNode, lines),
|
|
3405
3652
|
returnType: isConstructor ? null :
|
|
3406
3653
|
(identity.conversionType || returnTypeOf(templatedDeclaration(node))),
|
|
3407
3654
|
startLine,
|
|
3408
3655
|
endLine,
|
|
3656
|
+
sourceSpan,
|
|
3409
3657
|
...(identity.nameNode?.startPosition.row + 1 !== startLine && {
|
|
3410
3658
|
nameLine: identity.nameNode.startPosition.row + 1,
|
|
3411
3659
|
}),
|
|
@@ -3414,6 +3662,7 @@ function memberFromNode(node, className, access, lines, mode) {
|
|
|
3414
3662
|
memberType: isConstructor ? 'constructor' : 'method',
|
|
3415
3663
|
isMethod: true,
|
|
3416
3664
|
isConstructor,
|
|
3665
|
+
...(mode === 'cpp' && memberQualifiers(declarator)),
|
|
3417
3666
|
...(enclosingNamespace(node) && {
|
|
3418
3667
|
namespace: enclosingNamespace(node),
|
|
3419
3668
|
}),
|
|
@@ -3442,30 +3691,83 @@ function memberFromNode(node, className, access, lines, mode) {
|
|
|
3442
3691
|
};
|
|
3443
3692
|
}
|
|
3444
3693
|
|
|
3445
|
-
function fieldMembers(node, access, lines) {
|
|
3694
|
+
function fieldMembers(node, access, lines, paths = null) {
|
|
3446
3695
|
if (node.type !== 'field_declaration') return [];
|
|
3447
3696
|
if (functionDeclarator(node)) return [];
|
|
3448
3697
|
const typeNode = node.childForFieldName('type') ||
|
|
3449
3698
|
node.namedChildren.find(child => TYPE_NODES.has(child.type));
|
|
3450
3699
|
const fields = [];
|
|
3451
|
-
|
|
3452
|
-
|
|
3700
|
+
const declarators = node.childrenForFieldName('declarator');
|
|
3701
|
+
// Members of an unnamed struct or union (fix #430): without a declarator
|
|
3702
|
+
// (an anonymous member) they are members of the enclosing class; with
|
|
3703
|
+
// declarators they are reached through them (`e.data.start`), recorded
|
|
3704
|
+
// as the member paths from the enclosing class.
|
|
3705
|
+
const nestedBody = (typeNode?.type === 'struct_specifier' || typeNode?.type === 'union_specifier') &&
|
|
3706
|
+
!typeNode.childForFieldName('name') ? typeNode.childForFieldName('body') : null;
|
|
3707
|
+
if (nestedBody) {
|
|
3708
|
+
const declaredNames = declarators.map(child => declaratorIdentity(child).name).filter(Boolean);
|
|
3709
|
+
const nestedPaths = declaredNames.length === 0 ? paths
|
|
3710
|
+
: (paths || [[]]).flatMap(path => declaredNames.map(name => [...path, name]));
|
|
3711
|
+
for (const entry of memberListEntries(nestedBody)) {
|
|
3712
|
+
if (entry.type === 'field_declaration') fields.push(...fieldMembers(entry, access, lines, nestedPaths));
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
for (const child of declarators) {
|
|
3453
3716
|
const identity = declaratorIdentity(child);
|
|
3454
3717
|
if (!identity.name || identity.name === typeNode?.text) continue;
|
|
3455
|
-
const { startLine, endLine, indent } = nodeToLocation(child, lines);
|
|
3718
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(child, lines);
|
|
3719
|
+
// Pointer declarators of the field (`State* m_raw`): `->` on it
|
|
3720
|
+
// dereferences the pointer, on an object it calls operator-> (fix #427).
|
|
3721
|
+
let pointers = 0;
|
|
3722
|
+
let arrays = false;
|
|
3723
|
+
for (let current = child; current && !IDENTIFIER_NODES.has(current.type);) {
|
|
3724
|
+
if (current.type === 'pointer_declarator') pointers++;
|
|
3725
|
+
if (current.type === 'array_declarator' || current.type === 'function_declarator') arrays = true;
|
|
3726
|
+
current = current.childForFieldName('declarator') ||
|
|
3727
|
+
(current.namedChildren || []).find(next => next.type.endsWith('_declarator'));
|
|
3728
|
+
}
|
|
3456
3729
|
fields.push({
|
|
3457
3730
|
name: identity.name,
|
|
3458
3731
|
startLine,
|
|
3459
3732
|
endLine,
|
|
3733
|
+
sourceSpan,
|
|
3460
3734
|
indent,
|
|
3461
3735
|
modifiers: access ? [access] : [],
|
|
3462
3736
|
memberType: 'field',
|
|
3463
|
-
fieldType: typeNode?.text || null,
|
|
3737
|
+
fieldType: nestedBody ? null : typeNode?.text || null,
|
|
3738
|
+
...(!arrays && { fieldPointers: pointers }),
|
|
3739
|
+
...(paths?.length && { memberPaths: paths.map(path => [...path]) }),
|
|
3464
3740
|
});
|
|
3465
3741
|
}
|
|
3466
3742
|
return fields;
|
|
3467
3743
|
}
|
|
3468
3744
|
|
|
3745
|
+
/** The data members a member variable template declares (fix #430:
|
|
3746
|
+
* `template <class> static constexpr bool value = true;`). */
|
|
3747
|
+
function variableTemplateMembers(node, access, lines) {
|
|
3748
|
+
const declaration = (node.namedChildren || []).find(child => child.type === 'declaration');
|
|
3749
|
+
if (!declaration || functionDeclarator(declaration)) return [];
|
|
3750
|
+
const typeNode = declaration.childForFieldName('type');
|
|
3751
|
+
const members = [];
|
|
3752
|
+
for (const child of declaration.childrenForFieldName('declarator')) {
|
|
3753
|
+
const identity = declaratorIdentity(child);
|
|
3754
|
+
if (!identity.name) continue;
|
|
3755
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(child, lines);
|
|
3756
|
+
members.push({
|
|
3757
|
+
name: identity.name,
|
|
3758
|
+
startLine,
|
|
3759
|
+
endLine,
|
|
3760
|
+
sourceSpan,
|
|
3761
|
+
indent,
|
|
3762
|
+
modifiers: modifiersOf(declaration, access ? [access] : []),
|
|
3763
|
+
memberType: 'field',
|
|
3764
|
+
fieldType: typeNode?.text || null,
|
|
3765
|
+
...(templateParameterNames(declaration) && { templateParams: templateParameterNames(declaration) }),
|
|
3766
|
+
});
|
|
3767
|
+
}
|
|
3768
|
+
return members;
|
|
3769
|
+
}
|
|
3770
|
+
|
|
3469
3771
|
/**
|
|
3470
3772
|
* Member-list entries in document order, with preprocessor conditionals
|
|
3471
3773
|
* opened (fix #385): `#ifdef _WIN32 bool f() {...} #else bool f() {...}
|
|
@@ -3603,11 +3905,12 @@ function classMembers(node, lines, mode) {
|
|
|
3603
3905
|
(child.namedChildren || []).find(candidate =>
|
|
3604
3906
|
candidate.type === 'identifier');
|
|
3605
3907
|
if (nameNode?.text) {
|
|
3606
|
-
const { startLine, endLine, indent } = nodeToLocation(nameNode, lines);
|
|
3908
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(nameNode, lines);
|
|
3607
3909
|
members.push({
|
|
3608
3910
|
name: nameNode.text,
|
|
3609
3911
|
startLine,
|
|
3610
3912
|
endLine,
|
|
3913
|
+
sourceSpan,
|
|
3611
3914
|
indent,
|
|
3612
3915
|
modifiers: ['public'],
|
|
3613
3916
|
memberType: 'field',
|
|
@@ -3634,7 +3937,9 @@ function classMembers(node, lines, mode) {
|
|
|
3634
3937
|
const member = macroName ? null : memberFromNode(
|
|
3635
3938
|
child, identity.ownerName || identity.name, effectiveAccess, lines, mode);
|
|
3636
3939
|
if (member) members.push(member);
|
|
3637
|
-
else if (!macroName
|
|
3940
|
+
else if (!macroName && child.type === 'template_declaration') {
|
|
3941
|
+
members.push(...variableTemplateMembers(child, effectiveAccess, lines));
|
|
3942
|
+
} else if (!macroName) members.push(...fieldMembers(child, effectiveAccess, lines));
|
|
3638
3943
|
if (unknownAccess) {
|
|
3639
3944
|
for (let i = before; i < members.length; i++) {
|
|
3640
3945
|
members[i].accessAfterMacro = { ...unknownAccess };
|
|
@@ -3750,7 +4055,7 @@ function typedefEntries(node, lines) {
|
|
|
3750
4055
|
const identity = declaratorIdentity(child);
|
|
3751
4056
|
if (!identity.name || RESERVED_TYPE_KEYWORDS.has(identity.name)) continue;
|
|
3752
4057
|
if (innerIsClass && (!innerClassName || innerClassName === identity.name)) continue;
|
|
3753
|
-
const { startLine, endLine, indent } = nodeToLocation(child, lines);
|
|
4058
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(child, lines);
|
|
3754
4059
|
// A function-pointer typedef aliases a function shape, not the return
|
|
3755
4060
|
// type — record no aliasOf for it.
|
|
3756
4061
|
const aliasOf = unwrapDeclarator(child)
|
|
@@ -3761,6 +4066,7 @@ function typedefEntries(node, lines) {
|
|
|
3761
4066
|
type: 'type',
|
|
3762
4067
|
startLine,
|
|
3763
4068
|
endLine,
|
|
4069
|
+
sourceSpan,
|
|
3764
4070
|
...(identity.nameNode?.startPosition.row + 1 !== startLine && {
|
|
3765
4071
|
nameLine: identity.nameNode.startPosition.row + 1,
|
|
3766
4072
|
}),
|
|
@@ -3778,6 +4084,18 @@ function typedefEntries(node, lines) {
|
|
|
3778
4084
|
return entries;
|
|
3779
4085
|
}
|
|
3780
4086
|
|
|
4087
|
+
function classNamespaceScope(node, named) {
|
|
4088
|
+
const parts = [];
|
|
4089
|
+
let anonymous = false;
|
|
4090
|
+
for (let parent = node.parent; parent; parent = parent.parent) {
|
|
4091
|
+
if (parent.type !== 'namespace_definition') continue;
|
|
4092
|
+
const name = parent.childForFieldName('name');
|
|
4093
|
+
if (name) parts.unshift(name.text);
|
|
4094
|
+
else { parts.unshift('<anonymous>'); anonymous = true; }
|
|
4095
|
+
}
|
|
4096
|
+
return { anonymous, key: anonymous ? `${parts.join('::')}\0${named || ''}` : named || '' };
|
|
4097
|
+
}
|
|
4098
|
+
|
|
3781
4099
|
function findClassesInTree(code, tree, mode, sourceLines = null) {
|
|
3782
4100
|
const lines = sourceLines || code.split('\n');
|
|
3783
4101
|
const classes = [];
|
|
@@ -3786,7 +4104,7 @@ function findClassesInTree(code, tree, mode, sourceLines = null) {
|
|
|
3786
4104
|
// the same name (forward declarations, `struct S` in a parameter or
|
|
3787
4105
|
// field type position) are references to it, never second definitions.
|
|
3788
4106
|
const bodiedNames = new Set();
|
|
3789
|
-
const bodylessEntries = new
|
|
4107
|
+
const bodylessEntries = new Map();
|
|
3790
4108
|
const forwardDeclared = new Set();
|
|
3791
4109
|
traverseTreeCached(tree.rootNode, node => {
|
|
3792
4110
|
if (mode === 'cpp' && node.type === 'alias_declaration') {
|
|
@@ -3800,13 +4118,14 @@ function findClassesInTree(code, tree, mode, sourceLines = null) {
|
|
|
3800
4118
|
(child.type === 'type_descriptor' ||
|
|
3801
4119
|
TYPE_NODES.has(child.type)));
|
|
3802
4120
|
if (!nameNode?.text) return false;
|
|
3803
|
-
const { startLine, endLine, indent } =
|
|
4121
|
+
const { startLine, endLine, indent, sourceSpan } =
|
|
3804
4122
|
nodeToLocation(node, lines);
|
|
3805
4123
|
classes.push({
|
|
3806
4124
|
name: nameNode.text,
|
|
3807
4125
|
type: 'type',
|
|
3808
4126
|
startLine,
|
|
3809
4127
|
endLine,
|
|
4128
|
+
sourceSpan,
|
|
3810
4129
|
indent,
|
|
3811
4130
|
modifiers: ['public'],
|
|
3812
4131
|
members: [],
|
|
@@ -3827,24 +4146,43 @@ function findClassesInTree(code, tree, mode, sourceLines = null) {
|
|
|
3827
4146
|
const identity = classIdentity(node);
|
|
3828
4147
|
if (!identity?.name) return true;
|
|
3829
4148
|
const hasBody = !!node.childForFieldName('body');
|
|
4149
|
+
if (!hasBody) {
|
|
4150
|
+
let container = node.parent;
|
|
4151
|
+
while (container && (PP_CONDITIONAL_TYPES.has(container.type) ||
|
|
4152
|
+
container.type === 'template_declaration')) container = container.parent;
|
|
4153
|
+
if (!['translation_unit', 'declaration_list'].includes(container?.type)) return true;
|
|
4154
|
+
}
|
|
4155
|
+
const outer = identity.enclosingType ? null : mode === 'cpp' ? enclosingClass(node) : null;
|
|
4156
|
+
const enclosingType = identity.enclosingType || outer?.name || null;
|
|
4157
|
+
const namespace = identity.ownerQualifier
|
|
4158
|
+
? [enclosingNamespace(node), identity.ownerQualifier].filter(Boolean).join('::')
|
|
4159
|
+
: enclosingNamespace(node);
|
|
4160
|
+
const namespaceScope = classNamespaceScope(node, namespace);
|
|
4161
|
+
const localScope = functionLocalClassScope(node);
|
|
4162
|
+
const generics = mode === 'cpp' ? templateParameterNames(node) : null;
|
|
4163
|
+
const branch = conditionalBranchLines(node);
|
|
4164
|
+
const tag = node.type === 'union_specifier' || node.type === 'enum_specifier'
|
|
4165
|
+
? node.type : 'record';
|
|
4166
|
+
const scopeKey = `${namespaceScope.key}\0${enclosingType || ''}\0${identity.ownerName}` +
|
|
4167
|
+
`\0${generics?.length || 0}\0${tag}\0${localScope.lexicalScopeStartLine || 0}`;
|
|
3830
4168
|
if (hasBody) {
|
|
3831
|
-
|
|
4169
|
+
// A same-scope declaration denotes this class even when its
|
|
4170
|
+
// body is conditional. Keep every alternative body, without an
|
|
4171
|
+
// extra incomplete class competing with its own definition.
|
|
4172
|
+
bodiedNames.add(scopeKey);
|
|
3832
4173
|
} else {
|
|
3833
|
-
if (bodiedNames.has(
|
|
4174
|
+
if (bodiedNames.has(scopeKey)) return true;
|
|
3834
4175
|
// A bodyless specifier is only a DECLARATION at declaration
|
|
3835
4176
|
// level (`struct S;`); in a type position (`void f(struct S *)`)
|
|
3836
4177
|
// it is a reference. One entry per opaque forward-declared name.
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
if (forwardDeclared.has(identity.name)) return true;
|
|
3840
|
-
forwardDeclared.add(identity.name);
|
|
4178
|
+
if (forwardDeclared.has(scopeKey)) return true;
|
|
4179
|
+
forwardDeclared.add(scopeKey);
|
|
3841
4180
|
}
|
|
3842
4181
|
const key = `${node.startIndex}:${identity.name}`;
|
|
3843
4182
|
if (seen.has(key)) return false;
|
|
3844
4183
|
seen.add(key);
|
|
3845
|
-
const { startLine, endLine, indent } = nodeToLocation(node, lines);
|
|
3846
|
-
|
|
3847
|
-
if (type === 'union') type = 'type';
|
|
4184
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(node, lines);
|
|
4185
|
+
const type = node.type.replace('_specifier', '');
|
|
3848
4186
|
const baseClause = node.namedChildren.find(child => child.type === 'base_class_clause');
|
|
3849
4187
|
const bases = baseClause
|
|
3850
4188
|
? baseClause.namedChildren
|
|
@@ -3858,19 +4196,18 @@ function findClassesInTree(code, tree, mode, sourceLines = null) {
|
|
|
3858
4196
|
// A nested class names its enclosing class (fix #396): lexically
|
|
3859
4197
|
// (`class A { struct B { .. }; }`) or by its qualified definition
|
|
3860
4198
|
// (`struct A::B { .. }`, whose outer scopes qualify its namespace).
|
|
3861
|
-
const outer = identity.enclosingType ? null : mode === 'cpp' ? enclosingClass(node) : null;
|
|
3862
|
-
const enclosingType = identity.enclosingType || outer?.name || null;
|
|
3863
|
-
const namespace = identity.ownerQualifier
|
|
3864
|
-
? [enclosingNamespace(node), identity.ownerQualifier].filter(Boolean).join('::')
|
|
3865
|
-
: enclosingNamespace(node);
|
|
3866
4199
|
const entry = {
|
|
3867
4200
|
name: identity.name,
|
|
3868
4201
|
...(identity.ownerName !== identity.name && {
|
|
3869
4202
|
specialization: identity.ownerName,
|
|
3870
4203
|
}),
|
|
3871
4204
|
type,
|
|
4205
|
+
...(!hasBody && { forwardDeclaration: true }),
|
|
4206
|
+
...(namespaceScope.anonymous && { anonymousNamespace: true }),
|
|
4207
|
+
...(branch && { ppBranch: branch }),
|
|
3872
4208
|
startLine,
|
|
3873
4209
|
endLine,
|
|
4210
|
+
sourceSpan,
|
|
3874
4211
|
...(identity.nameNode?.startPosition.row + 1 !== startLine && {
|
|
3875
4212
|
nameLine: identity.nameNode.startPosition.row + 1,
|
|
3876
4213
|
}),
|
|
@@ -3881,21 +4218,21 @@ function findClassesInTree(code, tree, mode, sourceLines = null) {
|
|
|
3881
4218
|
...(namespace && { namespace }),
|
|
3882
4219
|
...(enclosingType && { enclosingType }),
|
|
3883
4220
|
...(bases.length > 0 && { extends: bases.join(', ') }),
|
|
3884
|
-
...(
|
|
3885
|
-
generics
|
|
4221
|
+
...(generics && {
|
|
4222
|
+
generics,
|
|
3886
4223
|
}),
|
|
3887
|
-
...
|
|
4224
|
+
...localScope,
|
|
3888
4225
|
docstring: extractJSDocstring(lines, startLine),
|
|
3889
4226
|
};
|
|
3890
4227
|
classes.push(entry);
|
|
3891
|
-
if (!hasBody) bodylessEntries.
|
|
4228
|
+
if (!hasBody) bodylessEntries.set(entry, scopeKey);
|
|
3892
4229
|
return true;
|
|
3893
4230
|
});
|
|
3894
4231
|
// A forward declaration can precede its body. Filtering after the single
|
|
3895
4232
|
// traversal preserves the old "body wins" result without paying a full
|
|
3896
4233
|
// preliminary tree walk merely to discover future bodied names.
|
|
3897
4234
|
return classes.filter(entry =>
|
|
3898
|
-
!bodylessEntries.has(entry) || !bodiedNames.has(entry
|
|
4235
|
+
!bodylessEntries.has(entry) || !bodiedNames.has(bodylessEntries.get(entry)));
|
|
3899
4236
|
}
|
|
3900
4237
|
|
|
3901
4238
|
function findClasses(code, parser, mode) {
|
|
@@ -3914,6 +4251,7 @@ function findClasses(code, parser, mode) {
|
|
|
3914
4251
|
|
|
3915
4252
|
function findFunctionsInTree(code, tree, mode, sourceLines = null) {
|
|
3916
4253
|
const lines = sourceLines || code.split('\n');
|
|
4254
|
+
let constructorDecorations = null;
|
|
3917
4255
|
const lineStarts = [0];
|
|
3918
4256
|
for (let index = 0; index < code.length; index++) {
|
|
3919
4257
|
if (code.charCodeAt(index) === 10) lineStarts.push(index + 1);
|
|
@@ -3931,7 +4269,16 @@ function findFunctionsInTree(code, tree, mode, sourceLines = null) {
|
|
|
3931
4269
|
}
|
|
3932
4270
|
if (!FUNCTION_CONTAINERS.has(node.type)) return true;
|
|
3933
4271
|
const declarator = functionDeclarator(node);
|
|
3934
|
-
if (!declarator)
|
|
4272
|
+
if (!declarator) {
|
|
4273
|
+
// `M(name) { .. }` read as a definition with no function
|
|
4274
|
+
// declarator (C, fix #427).
|
|
4275
|
+
const generated = node.type === 'function_definition' ? generatedCallableOf(node, lines) : null;
|
|
4276
|
+
if (generated) {
|
|
4277
|
+
functions.push(generated);
|
|
4278
|
+
return false;
|
|
4279
|
+
}
|
|
4280
|
+
return true;
|
|
4281
|
+
}
|
|
3935
4282
|
// A macro invocation read as a function is not one (fix #385); an
|
|
3936
4283
|
// invocation with a body at namespace scope defines the callable
|
|
3937
4284
|
// the expansion declares around that body (fix #391).
|
|
@@ -3943,6 +4290,8 @@ function findFunctionsInTree(code, tree, mode, sourceLines = null) {
|
|
|
3943
4290
|
const owner = enclosingClass(node);
|
|
3944
4291
|
const identity = declaratorIdentity(declarator);
|
|
3945
4292
|
if (!identity.name) return true;
|
|
4293
|
+
// A deduction guide declares no function (fix #427).
|
|
4294
|
+
if (mode === 'cpp' && deductionGuideOf(node)) return false;
|
|
3946
4295
|
// A name joined by `##` is replacement-list text of a macro the
|
|
3947
4296
|
// grammar could not read as a directive, never a declared function
|
|
3948
4297
|
// (fix #387).
|
|
@@ -3961,9 +4310,13 @@ function findFunctionsInTree(code, tree, mode, sourceLines = null) {
|
|
|
3961
4310
|
const location = nodeToLocation(node, lines);
|
|
3962
4311
|
const startLine = location.startLine;
|
|
3963
4312
|
const indent = location.indent;
|
|
3964
|
-
const
|
|
4313
|
+
const declaredType = templatedDeclaration(node).childForFieldName('type');
|
|
4314
|
+
const decorationType = mode === 'cpp' && identity.className &&
|
|
4315
|
+
!identity.name.startsWith('operator') && declaredType?.type === 'type_identifier' &&
|
|
4316
|
+
(constructorDecorations ||= new DecorationEvidence(require('./index').getParser(mode), code, tree))
|
|
4317
|
+
.defined(declaredType.text) === 'decoration';
|
|
3965
4318
|
const isConstructor = mode === 'cpp' && !!identity.className &&
|
|
3966
|
-
|
|
4319
|
+
!identity.name.startsWith('operator') && (!declaredType || decorationType);
|
|
3967
4320
|
const namespace = [enclosingNamespace(node), identity.className && identity.ownerQualifier]
|
|
3968
4321
|
.filter(Boolean).join('::');
|
|
3969
4322
|
const lexicalEnd = functionRangeEnd(
|
|
@@ -3972,6 +4325,7 @@ function findFunctionsInTree(code, tree, mode, sourceLines = null) {
|
|
|
3972
4325
|
? location.endLine
|
|
3973
4326
|
: lineNumberAtIndex(lineStarts, Math.max(0, lexicalEnd - 1));
|
|
3974
4327
|
const modifiers = modifiersOf(node);
|
|
4328
|
+
if (isConstructor && identity.name.startsWith('~')) modifiers.push('destructor');
|
|
3975
4329
|
if (friendOf && !modifiers.includes('friend')) modifiers.unshift('friend');
|
|
3976
4330
|
if (!modifiers.includes('static')) modifiers.push('export');
|
|
3977
4331
|
const returnedConcreteType = mode === 'cpp' && variableTypes
|
|
@@ -3979,8 +4333,10 @@ function findFunctionsInTree(code, tree, mode, sourceLines = null) {
|
|
|
3979
4333
|
: null;
|
|
3980
4334
|
functions.push({
|
|
3981
4335
|
name: identity.name,
|
|
3982
|
-
|
|
3983
|
-
|
|
4336
|
+
sourceSpan: lexicalEnd == null ? location.sourceSpan : { ...location.sourceSpan,
|
|
4337
|
+
end: lexicalEnd, endLine, endColumn: lexicalEnd - lineStarts[endLine - 1] },
|
|
4338
|
+
params: paramsNode ? paramsDisplayText(paramsNode, lines) : '...',
|
|
4339
|
+
paramsStructured: structuredParams(paramsNode, lines),
|
|
3984
4340
|
returnType: isConstructor ? null :
|
|
3985
4341
|
(identity.conversionType || returnTypeOf(node)),
|
|
3986
4342
|
startLine,
|
|
@@ -3996,6 +4352,7 @@ function findFunctionsInTree(code, tree, mode, sourceLines = null) {
|
|
|
3996
4352
|
receiver: identity.className,
|
|
3997
4353
|
isMethod: true,
|
|
3998
4354
|
}),
|
|
4355
|
+
...(mode === 'cpp' && identity.className && memberQualifiers(declarator)),
|
|
3999
4356
|
...(friendOf && { friendOf: friendOf.ownerName || friendOf.name }),
|
|
4000
4357
|
...(mode === 'cpp' && isTemplateDependentCallable(node) && {
|
|
4001
4358
|
templateDependent: true,
|
|
@@ -4119,6 +4476,22 @@ function findFunctions(code, parser, mode) {
|
|
|
4119
4476
|
|
|
4120
4477
|
function findStateObjectsInTree(tree, lines) {
|
|
4121
4478
|
const states = [];
|
|
4479
|
+
// An unnamed enum without a typedef owner declares values directly
|
|
4480
|
+
// in its enclosing scope. A typedef's members already carry them.
|
|
4481
|
+
traverseTreeCached(tree.rootNode, node => {
|
|
4482
|
+
if (node.type !== 'enum_specifier' || classIdentity(node)) return true;
|
|
4483
|
+
const body = node.childForFieldName('body');
|
|
4484
|
+
for (const member of body?.namedChildren || []) {
|
|
4485
|
+
if (member.type !== 'enumerator') continue;
|
|
4486
|
+
const name = member.childForFieldName('name');
|
|
4487
|
+
if (!name) continue;
|
|
4488
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(name, lines);
|
|
4489
|
+
const namespace = enclosingNamespace(node);
|
|
4490
|
+
states.push({ name: name.text, startLine, endLine, sourceSpan, indent,
|
|
4491
|
+
...(namespace && { namespace }) });
|
|
4492
|
+
}
|
|
4493
|
+
return true;
|
|
4494
|
+
});
|
|
4122
4495
|
traverseTreeCached(tree.rootNode, node => {
|
|
4123
4496
|
if (node.type !== 'declaration') return true;
|
|
4124
4497
|
if (functionDeclarator(node)) return false;
|
|
@@ -4127,18 +4500,29 @@ function findStateObjectsInTree(tree, lines) {
|
|
|
4127
4500
|
// an #if/#else remain part of the source inventory even though only
|
|
4128
4501
|
// one arm can exist in any particular build configuration.
|
|
4129
4502
|
let scope = node.parent;
|
|
4130
|
-
|
|
4503
|
+
// A variable template's `template <..>` head is transparent too
|
|
4504
|
+
// (fix #430: `template <class T> inline constexpr bool v = ..;`);
|
|
4505
|
+
// a qualified declarator under it defines a class template's static
|
|
4506
|
+
// member (`template <class T> bool H<T>::x = false;`), no variable.
|
|
4507
|
+
let templated = false;
|
|
4508
|
+
while (scope && (/^preproc_/.test(scope.type) || scope.type === 'template_declaration')) {
|
|
4509
|
+
if (scope.type === 'template_declaration') templated = true;
|
|
4510
|
+
scope = scope.parent;
|
|
4511
|
+
}
|
|
4131
4512
|
if (scope?.type !== 'translation_unit' && scope?.type !== 'declaration_list') return false;
|
|
4132
4513
|
for (const child of node.namedChildren || []) {
|
|
4133
4514
|
const identity = declaratorIdentity(child);
|
|
4134
4515
|
if (!identity.name || TYPE_NODES.has(child.type)) continue;
|
|
4135
|
-
|
|
4516
|
+
if (templated && qualifiedDeclarator(child)) continue;
|
|
4517
|
+
const { startLine, endLine, indent, sourceSpan } = nodeToLocation(child, lines);
|
|
4136
4518
|
states.push({
|
|
4137
4519
|
name: identity.name,
|
|
4138
4520
|
startLine,
|
|
4139
4521
|
endLine,
|
|
4522
|
+
sourceSpan,
|
|
4140
4523
|
indent,
|
|
4141
4524
|
modifiers: modifiersOf(node),
|
|
4525
|
+
...(enclosingNamespace(node) && { namespace: enclosingNamespace(node) }),
|
|
4142
4526
|
});
|
|
4143
4527
|
}
|
|
4144
4528
|
return false;
|
|
@@ -4400,8 +4784,10 @@ function findCppUsingFacts(tree) {
|
|
|
4400
4784
|
if (parent.type === 'namespace_definition' ||
|
|
4401
4785
|
parent.type === 'translation_unit') break;
|
|
4402
4786
|
}
|
|
4787
|
+
const groups = conditionalGroupsOf(node);
|
|
4403
4788
|
const location = {
|
|
4404
4789
|
line: node.startPosition.row + 1,
|
|
4790
|
+
...(groups.length > 0 && { ppGroups: groups }),
|
|
4405
4791
|
...(enclosingNamespace(node) && { namespace: enclosingNamespace(node) }),
|
|
4406
4792
|
...(block && {
|
|
4407
4793
|
scopeStartLine: block.startPosition.row + 1,
|
|
@@ -4441,6 +4827,29 @@ const PP_CONDITIONAL_TYPES = new Set([
|
|
|
4441
4827
|
'preproc_if', 'preproc_ifdef', 'preproc_elif', 'preproc_elifdef', 'preproc_else',
|
|
4442
4828
|
]);
|
|
4443
4829
|
|
|
4830
|
+
/** Original preprocessor groups containing a declaration (fix #413). */
|
|
4831
|
+
function conditionalGroupsOf(node) {
|
|
4832
|
+
const groups = [];
|
|
4833
|
+
const seen = new Set();
|
|
4834
|
+
for (let up = node.parent; up; up = up.parent) {
|
|
4835
|
+
if (!PP_CONDITIONAL_TYPES.has(up.type)) continue;
|
|
4836
|
+
let head = up;
|
|
4837
|
+
while (head.parent && PP_CONDITIONAL_TYPES.has(head.parent.type) &&
|
|
4838
|
+
sameNode(head.parent.childForFieldName('alternative'), head)) head = head.parent;
|
|
4839
|
+
if (seen.has(head.startIndex) || isIncludeGuard(head)) continue;
|
|
4840
|
+
seen.add(head.startIndex);
|
|
4841
|
+
const branches = [];
|
|
4842
|
+
for (let arm = head; arm; arm = arm.childForFieldName('alternative')) {
|
|
4843
|
+
const next = arm.childForFieldName('alternative');
|
|
4844
|
+
branches.push([arm.startPosition.row + 1,
|
|
4845
|
+
next ? next.startPosition.row : arm.endPosition.row + 1]);
|
|
4846
|
+
}
|
|
4847
|
+
groups.push({ start: head.startPosition.row + 1,
|
|
4848
|
+
end: head.endPosition.row + 1, branches });
|
|
4849
|
+
}
|
|
4850
|
+
return groups;
|
|
4851
|
+
}
|
|
4852
|
+
|
|
4444
4853
|
/** `#ifndef G` whose first directive is `#define G` and that has no else. */
|
|
4445
4854
|
function isIncludeGuard(node) {
|
|
4446
4855
|
if (node.type !== 'preproc_ifdef' || node.child(0)?.type !== '#ifndef') return false;
|
|
@@ -4824,7 +5233,22 @@ function generatedCallableOf(node, lines) {
|
|
|
4824
5233
|
let macroNode;
|
|
4825
5234
|
let argsNode;
|
|
4826
5235
|
let body;
|
|
4827
|
-
if (node.type === 'function_definition'
|
|
5236
|
+
if (node.type === 'function_definition' &&
|
|
5237
|
+
node.childForFieldName('declarator')?.type === 'parenthesized_declarator') {
|
|
5238
|
+
// C reads `M(name) { .. }` as a definition of `name` returning the
|
|
5239
|
+
// type `M` (fix #427); a function definition needs a function
|
|
5240
|
+
// declarator, so the shape is an invocation with a body.
|
|
5241
|
+
const declarator = node.childForFieldName('declarator');
|
|
5242
|
+
const typeNode = node.childForFieldName('type');
|
|
5243
|
+
body = node.childForFieldName('body');
|
|
5244
|
+
if (body?.type !== 'compound_statement' || typeNode?.type !== 'type_identifier' ||
|
|
5245
|
+
declarator.namedChildCount !== 1 || declarator.namedChild(0).type !== 'identifier' ||
|
|
5246
|
+
(node.namedChildren || []).some(child => child.type === 'storage_class_specifier' ||
|
|
5247
|
+
child.type === 'type_qualifier')) return null;
|
|
5248
|
+
macroNode = { type: 'identifier', text: typeNode.text };
|
|
5249
|
+
argsNode = declarator;
|
|
5250
|
+
head = node;
|
|
5251
|
+
} else if (node.type === 'function_definition') {
|
|
4828
5252
|
const declarator = node.childForFieldName('declarator');
|
|
4829
5253
|
body = node.childForFieldName('body');
|
|
4830
5254
|
if (body?.type !== 'compound_statement' || declarator?.type !== 'function_declarator') return null;
|
|
@@ -4882,20 +5306,100 @@ function generatedCallableOf(node, lines) {
|
|
|
4882
5306
|
const startLine = head.startPosition.row + 1;
|
|
4883
5307
|
const endLine = body.endPosition.row + 1;
|
|
4884
5308
|
const namespace = enclosingNamespace(head);
|
|
5309
|
+
// The function the expansion declares, when the project defines the
|
|
5310
|
+
// macro (fix #427): the file's own definition in effect, else the
|
|
5311
|
+
// headers' function-head macros.
|
|
5312
|
+
let templates = definition ? [definition] : null;
|
|
5313
|
+
if (!templates && externalMacroContext?.functionHeads?.has(macroName)) {
|
|
5314
|
+
templates = externalMacroContext.functionHeads.get(macroName);
|
|
5315
|
+
externalMacroContext.consulted?.add(macroName);
|
|
5316
|
+
}
|
|
5317
|
+
const expanded = templates && expandedFunctionHead(templates, argsNode.text, isCppTree(node.tree));
|
|
4885
5318
|
return {
|
|
4886
|
-
name,
|
|
4887
|
-
params: '...',
|
|
4888
|
-
|
|
5319
|
+
name: expanded?.name || name,
|
|
5320
|
+
params: expanded?.params ?? '...',
|
|
5321
|
+
...(expanded && { paramsStructured: expanded.paramsStructured }),
|
|
5322
|
+
returnType: expanded?.returnType || null,
|
|
4889
5323
|
startLine,
|
|
4890
5324
|
endLine,
|
|
4891
5325
|
indent: head.startPosition.column,
|
|
4892
|
-
modifiers: [],
|
|
5326
|
+
modifiers: expanded?.modifiers || [],
|
|
4893
5327
|
...(namespace && { namespace }),
|
|
4894
|
-
generatedByMacro: { name: macroName, ...(names.length > 0 && { args: names })
|
|
5328
|
+
generatedByMacro: { name: macroName, ...(names.length > 0 && { args: names }),
|
|
5329
|
+
...(expanded && { expanded: true }) },
|
|
4895
5330
|
docstring: extractJSDocstring(lines, startLine),
|
|
4896
5331
|
};
|
|
4897
5332
|
}
|
|
4898
5333
|
|
|
5334
|
+
/**
|
|
5335
|
+
* The function a function-like macro invocation's expansion declares (fix
|
|
5336
|
+
* #427): the replacement list with the invocation's arguments substituted
|
|
5337
|
+
* (`#` stringizes, `##` pastes), read as a function head. Its name,
|
|
5338
|
+
* parameters, return type and storage class, when every definition gives
|
|
5339
|
+
* one unqualified function; null otherwise (a nested macro, a member, a
|
|
5340
|
+
* head the grammar cannot read).
|
|
5341
|
+
*/
|
|
5342
|
+
function expandedFunctionHead(templates, argsText, cpp) {
|
|
5343
|
+
const args = splitInvocationArguments(argsText).map(arg => arg.trim());
|
|
5344
|
+
let result = null;
|
|
5345
|
+
for (const template of templates) {
|
|
5346
|
+
const params = template.params || [];
|
|
5347
|
+
const variadicIndex = params.indexOf('__VA_ARGS__');
|
|
5348
|
+
if (variadicIndex < 0 ? args.length !== params.length && !(params.length === 0 && args.length === 1 && !args[0])
|
|
5349
|
+
: args.length < variadicIndex) return null;
|
|
5350
|
+
const valueOf = param => {
|
|
5351
|
+
const at = params.indexOf(param);
|
|
5352
|
+
if (at < 0) return null;
|
|
5353
|
+
return at === variadicIndex ? args.slice(at).join(', ') : (args[at] ?? '');
|
|
5354
|
+
};
|
|
5355
|
+
const tokens = lexPP(String(template.body || ''));
|
|
5356
|
+
const out = [];
|
|
5357
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
5358
|
+
const token = tokens[i];
|
|
5359
|
+
if (token.v === '#' && tokens[i + 1]?.k === 'id' && valueOf(tokens[i + 1].v) != null) {
|
|
5360
|
+
out.push(JSON.stringify(valueOf(tokens[i + 1].v)));
|
|
5361
|
+
i++;
|
|
5362
|
+
continue;
|
|
5363
|
+
}
|
|
5364
|
+
if (token.v === '##') {
|
|
5365
|
+
const right = tokens[i + 1];
|
|
5366
|
+
if (!right || out.length === 0) return null;
|
|
5367
|
+
const value = right.k === 'id' && valueOf(right.v) != null ? valueOf(right.v) : right.v;
|
|
5368
|
+
out[out.length - 1] = `${out[out.length - 1]}${value}`;
|
|
5369
|
+
i++;
|
|
5370
|
+
continue;
|
|
5371
|
+
}
|
|
5372
|
+
out.push(token.k === 'id' && valueOf(token.v) != null ? valueOf(token.v) : token.v);
|
|
5373
|
+
}
|
|
5374
|
+
const text = out.join(' ');
|
|
5375
|
+
const parser = require('./index').getParser(cpp ? 'cpp' : 'c');
|
|
5376
|
+
const tree = parser && safeParse(parser, `${text} { }`, undefined, PARSE_OPTIONS);
|
|
5377
|
+
try {
|
|
5378
|
+
const definition = tree && !tree.rootNode.hasError && tree.rootNode.namedChildCount === 1
|
|
5379
|
+
? tree.rootNode.namedChild(0) : null;
|
|
5380
|
+
if (definition?.type !== 'function_definition') return null;
|
|
5381
|
+
const declarator = functionDeclarator(definition);
|
|
5382
|
+
const identity = declarator && declaratorIdentity(declarator);
|
|
5383
|
+
if (!identity?.name || identity.className || !/^[A-Za-z_]\w*$/.test(identity.name)) return null;
|
|
5384
|
+
const paramsNode = parameterListOf(declarator);
|
|
5385
|
+
const modifiers = modifiersOf(definition);
|
|
5386
|
+
if (!modifiers.includes('static')) modifiers.push('export');
|
|
5387
|
+
const head = {
|
|
5388
|
+
name: identity.name,
|
|
5389
|
+
params: paramsNode ? nodeTextWithoutComments(paramsNode).replace(/^\(|\)$/g, '').trim() : '',
|
|
5390
|
+
paramsStructured: structuredParams(paramsNode),
|
|
5391
|
+
returnType: returnTypeOf(definition),
|
|
5392
|
+
modifiers,
|
|
5393
|
+
};
|
|
5394
|
+
if (result && (result.name !== head.name || result.params !== head.params)) return null;
|
|
5395
|
+
result = head;
|
|
5396
|
+
} finally {
|
|
5397
|
+
tree?.delete?.();
|
|
5398
|
+
}
|
|
5399
|
+
}
|
|
5400
|
+
return result;
|
|
5401
|
+
}
|
|
5402
|
+
|
|
4899
5403
|
function macroInvocationDeclaration(node) {
|
|
4900
5404
|
if (!FUNCTION_CONTAINERS.has(node.type) || node.childForFieldName('type')) return false;
|
|
4901
5405
|
const declarator = node.childForFieldName('declarator');
|
|
@@ -5081,6 +5585,8 @@ function buildVariableTypes(tree) {
|
|
|
5081
5585
|
const memberReceiverUses = [];
|
|
5082
5586
|
const ambiguousDirectInitializers = [];
|
|
5083
5587
|
const autoElementInitializers = [];
|
|
5588
|
+
const autoCopyInitializers = [];
|
|
5589
|
+
const decltypeDeclarations = [];
|
|
5084
5590
|
const declaratorStaticType = (type, declarator, holder) => {
|
|
5085
5591
|
// The declaration's type node carries only the base type. Preserve
|
|
5086
5592
|
// array rank from the AST declarator for overload arguments:
|
|
@@ -5096,7 +5602,10 @@ function buildVariableTypes(tree) {
|
|
|
5096
5602
|
if (current.type === 'array_declarator') arrays++;
|
|
5097
5603
|
for (const child of current.namedChildren || []) stack.push(child);
|
|
5098
5604
|
}
|
|
5099
|
-
|
|
5605
|
+
const qualifiers = (holder?.children || []).filter(child => child.type === 'type_qualifier')
|
|
5606
|
+
.map(child => child.text).join(' ');
|
|
5607
|
+
const qualified = qualifiers ? `${qualifiers} ${type}` : type;
|
|
5608
|
+
if (arrays > 0) return `${qualified}${'[]'.repeat(arrays)}`;
|
|
5100
5609
|
// Pointer levels are part of the argument's type (fix #393):
|
|
5101
5610
|
// `const char* format` passes a C string, never a `char`. The chain
|
|
5102
5611
|
// of declarators is followed (an initializer's own declarators are
|
|
@@ -5105,7 +5614,7 @@ function buildVariableTypes(tree) {
|
|
|
5105
5614
|
// decides overloads (`f(char*)` versus `f(const char*)`).
|
|
5106
5615
|
let pointers = 0;
|
|
5107
5616
|
for (let current = declarator; current;) {
|
|
5108
|
-
if (current.type === 'function_declarator') return
|
|
5617
|
+
if (current.type === 'function_declarator') return qualified;
|
|
5109
5618
|
if (current.type === 'pointer_declarator') pointers++;
|
|
5110
5619
|
current = current.type === 'init_declarator' || current.type === 'pointer_declarator' ||
|
|
5111
5620
|
current.type === 'reference_declarator' || current.type === 'parenthesized_declarator'
|
|
@@ -5114,12 +5623,18 @@ function buildVariableTypes(tree) {
|
|
|
5114
5623
|
child.type.endsWith('declarator') || child.type === 'identifier'))
|
|
5115
5624
|
: null;
|
|
5116
5625
|
}
|
|
5117
|
-
if (pointers === 0) return
|
|
5118
|
-
|
|
5119
|
-
child.type === 'type_qualifier' && child.text === 'const');
|
|
5120
|
-
return `${pointeeConst ? 'const ' : ''}${type} ${'*'.repeat(pointers)}`;
|
|
5626
|
+
if (pointers === 0) return qualified;
|
|
5627
|
+
return `${qualified} ${'*'.repeat(pointers)}`;
|
|
5121
5628
|
};
|
|
5122
|
-
const addBindings = (node, type, pointeeType, scope, declarators, containerElementType, typeNamespace
|
|
5629
|
+
const addBindings = (node, type, pointeeType, scope, declarators, containerElementType, typeNamespace,
|
|
5630
|
+
constKnown = true) => {
|
|
5631
|
+
const writtenType = node.childForFieldName('type');
|
|
5632
|
+
const staticBase = writtenType && typeName(writtenType) === type ? writtenType.text : type;
|
|
5633
|
+
// The decl-specifier `const` qualifies the object a value or
|
|
5634
|
+
// reference names, or what a pointer points at (fix #427).
|
|
5635
|
+
const declConst = constKnown
|
|
5636
|
+
? (node.children || []).some(child => child.type === 'type_qualifier' && child.text === 'const')
|
|
5637
|
+
: undefined;
|
|
5123
5638
|
for (const declarator of declarators) {
|
|
5124
5639
|
const identity = declaratorIdentity(declarator);
|
|
5125
5640
|
if (!identity.name) continue;
|
|
@@ -5146,13 +5661,15 @@ function buildVariableTypes(tree) {
|
|
|
5146
5661
|
...(elementType && { elementType }),
|
|
5147
5662
|
name: identity.name,
|
|
5148
5663
|
type,
|
|
5149
|
-
staticType: declaratorStaticType(
|
|
5664
|
+
staticType: declaratorStaticType(staticBase, declarator, node),
|
|
5665
|
+
staticOrigin: writtenType && typeName(writtenType) === type ? argumentTypeContext(writtenType) : null,
|
|
5150
5666
|
origin: typeOrigin(node.type === 'declaration' &&
|
|
5151
5667
|
(declarator.type === 'identifier' ||
|
|
5152
5668
|
declarator.childForFieldName('value')?.type === 'new_expression')
|
|
5153
5669
|
? 'constructor' : 'annotation', declarator),
|
|
5154
5670
|
...(pointeeType && { pointeeType }),
|
|
5155
5671
|
...(pointers === 0 && arrays === 0 && { valueDeclarator: true }),
|
|
5672
|
+
...(declConst !== undefined && arrays === 0 && pointers <= 1 && { declConst, pointers }),
|
|
5156
5673
|
...(typeNamespace?.name === type && { typeNamespace: typeNamespace.namespace }),
|
|
5157
5674
|
declaredAt: node.type === 'parameter_declaration'
|
|
5158
5675
|
? scope.startIndex : declarator.startIndex,
|
|
@@ -5190,6 +5707,19 @@ function buildVariableTypes(tree) {
|
|
|
5190
5707
|
}
|
|
5191
5708
|
const typeNode = node.childForFieldName('type') ||
|
|
5192
5709
|
(node.namedChildren || []).find(child => TYPE_NODES.has(child.type));
|
|
5710
|
+
// `decltype(x) v` declares v with x's declared type; any other
|
|
5711
|
+
// decltype (`decltype(auto)`, an expression) leaves v untyped, never
|
|
5712
|
+
// a type named by the decltype's text (fix #427).
|
|
5713
|
+
if (typeNode?.type === 'decltype' || (typeNode?.type === 'placeholder_type_specifier' &&
|
|
5714
|
+
(typeNode.namedChildren || []).some(child => child.type === 'decltype'))) {
|
|
5715
|
+
const operand = typeNode.type === 'decltype' && typeNode.namedChildCount === 1
|
|
5716
|
+
? typeNode.namedChild(0) : null;
|
|
5717
|
+
if (operand?.type === 'identifier') {
|
|
5718
|
+
decltypeDeclarations.push({ node, operand, scope: variableBindingScope(node),
|
|
5719
|
+
declarators: variableDeclarators(node) });
|
|
5720
|
+
}
|
|
5721
|
+
return true;
|
|
5722
|
+
}
|
|
5193
5723
|
const type = typeName(typeNode);
|
|
5194
5724
|
if (type === 'auto') {
|
|
5195
5725
|
// `auto &c = items[i]` binds the declared element (fix #359);
|
|
@@ -5203,6 +5733,20 @@ function buildVariableTypes(tree) {
|
|
|
5203
5733
|
autoElementInitializers.push({
|
|
5204
5734
|
node, declarator, root, scope: variableBindingScope(node),
|
|
5205
5735
|
});
|
|
5736
|
+
continue;
|
|
5737
|
+
}
|
|
5738
|
+
// `auto x = b`, `auto &r = b`, `auto *p = &b` deduce b's type
|
|
5739
|
+
// (fix #434): an object copied, referenced, or pointed at by
|
|
5740
|
+
// the declarator's own pointer. A pointer source keeps its
|
|
5741
|
+
// value untyped (the declarator would read as an object).
|
|
5742
|
+
const declared = declarator.childForFieldName('declarator');
|
|
5743
|
+
const pointerDeclarator = declared?.type === 'pointer_declarator';
|
|
5744
|
+
const source = pointerDeclarator
|
|
5745
|
+
? (value?.type === 'pointer_expression' && value.children.some(child => child.type === '&')
|
|
5746
|
+
? value.childForFieldName('argument') : null)
|
|
5747
|
+
: value;
|
|
5748
|
+
if (source?.type === 'identifier') {
|
|
5749
|
+
autoCopyInitializers.push({ node, declarator, source, scope: variableBindingScope(node) });
|
|
5206
5750
|
}
|
|
5207
5751
|
}
|
|
5208
5752
|
return true;
|
|
@@ -5295,6 +5839,40 @@ function buildVariableTypes(tree) {
|
|
|
5295
5839
|
[candidate.declarator]);
|
|
5296
5840
|
}
|
|
5297
5841
|
}
|
|
5842
|
+
// In source order, each sees the bindings before it (fix #434).
|
|
5843
|
+
autoCopyInitializers.sort((a, b) => a.node.startIndex - b.node.startIndex);
|
|
5844
|
+
for (const candidate of autoCopyInitializers) {
|
|
5845
|
+
const at = candidate.source.startIndex;
|
|
5846
|
+
const sources = bindings.filter(binding =>
|
|
5847
|
+
binding.name === candidate.source.text &&
|
|
5848
|
+
binding.scopeStart <= at && at < binding.scopeEnd &&
|
|
5849
|
+
binding.declaredAt <= at);
|
|
5850
|
+
sources.sort((a, b) => (a.scopeEnd - a.scopeStart) - (b.scopeEnd - b.scopeStart) ||
|
|
5851
|
+
b.declaredAt - a.declaredAt);
|
|
5852
|
+
const source = sources[0];
|
|
5853
|
+
if (source?.type && source.valueDeclarator && !source.pointeeType) {
|
|
5854
|
+
addBindings(candidate.node, source.type, undefined, candidate.scope, [candidate.declarator],
|
|
5855
|
+
undefined, undefined, false);
|
|
5856
|
+
}
|
|
5857
|
+
}
|
|
5858
|
+
// In source order: `decltype(copy) c2` sees the binding `copy` got.
|
|
5859
|
+
decltypeDeclarations.sort((a, b) => a.node.startIndex - b.node.startIndex);
|
|
5860
|
+
for (const candidate of decltypeDeclarations) {
|
|
5861
|
+
const at = candidate.operand.startIndex;
|
|
5862
|
+
const operands = bindings.filter(binding =>
|
|
5863
|
+
binding.name === candidate.operand.text &&
|
|
5864
|
+
binding.scopeStart <= at && at < binding.scopeEnd &&
|
|
5865
|
+
binding.declaredAt <= at);
|
|
5866
|
+
operands.sort((a, b) => (a.scopeEnd - a.scopeStart) - (b.scopeEnd - b.scopeStart) ||
|
|
5867
|
+
b.declaredAt - a.declaredAt);
|
|
5868
|
+
const operand = operands[0];
|
|
5869
|
+
// An object (not a pointer) of a named type: the declared variable
|
|
5870
|
+
// has its type.
|
|
5871
|
+
if (operand?.type && operand.valueDeclarator && !operand.pointeeType) {
|
|
5872
|
+
addBindings(candidate.node, operand.type, undefined, candidate.scope, candidate.declarators,
|
|
5873
|
+
undefined, undefined, false);
|
|
5874
|
+
}
|
|
5875
|
+
}
|
|
5298
5876
|
// Bindings are complete before call extraction. Partition by spelling
|
|
5299
5877
|
// once: every argument and receiver lookup otherwise scans all locals
|
|
5300
5878
|
// in the translation unit, including unrelated functions.
|
|
@@ -5318,6 +5896,7 @@ function buildVariableTypes(tree) {
|
|
|
5318
5896
|
return {
|
|
5319
5897
|
get: (name, atNode) => resolveBinding(name, atNode)?.type,
|
|
5320
5898
|
getStatic: (name, atNode) => resolveBinding(name, atNode)?.staticType,
|
|
5899
|
+
getStaticOrigin: (name, atNode) => resolveBinding(name, atNode)?.staticOrigin,
|
|
5321
5900
|
evidence: (name, atNode) => {
|
|
5322
5901
|
const binding = resolveBinding(name, atNode);
|
|
5323
5902
|
return { receiverTypeSource: binding?.origin?.source || 'unknown',
|
|
@@ -5327,6 +5906,14 @@ function buildVariableTypes(tree) {
|
|
|
5327
5906
|
},
|
|
5328
5907
|
getPointee: (name, atNode) =>
|
|
5329
5908
|
resolveBinding(name, atNode)?.pointeeType,
|
|
5909
|
+
// Is the object `name` denotes (through `->` when `arrow`) const?
|
|
5910
|
+
// true / false where the declaration decides, else undefined.
|
|
5911
|
+
objectConst: (name, atNode, arrow) => {
|
|
5912
|
+
const binding = resolveBinding(name, atNode);
|
|
5913
|
+
if (!binding || binding.declConst === undefined) return undefined;
|
|
5914
|
+
if (arrow ? binding.pointers !== 1 : binding.pointers !== 0) return undefined;
|
|
5915
|
+
return binding.declConst;
|
|
5916
|
+
},
|
|
5330
5917
|
// fix #379: `v->m()` on a variable declared as an object (no
|
|
5331
5918
|
// pointer declarator) goes through the class's operator->: the
|
|
5332
5919
|
// declared type is a smart pointer or iterator (possibly behind an
|
|
@@ -5396,14 +5983,20 @@ function staticArgumentKind(node, variableTypes) {
|
|
|
5396
5983
|
}
|
|
5397
5984
|
if (node.type === 'number_literal') {
|
|
5398
5985
|
const text = node.text || '';
|
|
5986
|
+
// Integer literal zero is a null pointer constant, including its
|
|
5987
|
+
// base, digit separators and standard integer suffixes.
|
|
5988
|
+
if (/^(?:0[xX]0+|0[bB]0+|0+)(?:[uU](?:ll|LL|[lLzZ])?|(?:ll|LL|[lLzZ])[uU]?)?$/
|
|
5989
|
+
.test(text.replace(/'/g, ''))) return 'number:zero';
|
|
5990
|
+
const hexadecimal = /^0[xX]/.test(text);
|
|
5399
5991
|
const floating = text.includes('.') ||
|
|
5400
5992
|
/[pP][+-]?[0-9]/.test(text) ||
|
|
5401
|
-
/[eE][+-]?[0-9]/.test(text) ||
|
|
5402
|
-
/[fF]$/.test(text);
|
|
5993
|
+
!hexadecimal && (/[eE][+-]?[0-9]/.test(text) || /[fF]$/.test(text));
|
|
5403
5994
|
return floating ? 'number:floating' : 'number:integer';
|
|
5404
5995
|
}
|
|
5405
5996
|
if (node.type === 'true' || node.type === 'false') return 'bool';
|
|
5406
|
-
|
|
5997
|
+
// The grammar also calls NULL a null node, but that macro can expand
|
|
5998
|
+
// to integer zero. Only the keyword has nullptr's conversion rules.
|
|
5999
|
+
if (node.type === 'null' || node.type === 'nullptr') return node.text === 'nullptr' ? 'null' : 'expr';
|
|
5407
6000
|
if (node.type === 'identifier') {
|
|
5408
6001
|
const type = variableTypes?.getStatic(node.text, node) ||
|
|
5409
6002
|
variableTypes?.get(node.text, node);
|
|
@@ -5414,21 +6007,21 @@ function staticArgumentKind(node, variableTypes) {
|
|
|
5414
6007
|
(node.namedChildren || []).find(child =>
|
|
5415
6008
|
TYPE_NODES.has(child.type) || child.type === 'type_descriptor');
|
|
5416
6009
|
const type = typeName(typeNode);
|
|
5417
|
-
return type ? `type:${
|
|
6010
|
+
return type ? `type:${typeNode.text}` : 'expr';
|
|
5418
6011
|
}
|
|
5419
6012
|
if (node.type === 'new_expression') {
|
|
5420
6013
|
const typeNode = node.childForFieldName('type') ||
|
|
5421
6014
|
(node.namedChildren || []).find(child =>
|
|
5422
6015
|
TYPE_NODES.has(child.type) || child.type === 'type_descriptor');
|
|
5423
6016
|
const type = typeName(typeNode);
|
|
5424
|
-
return type ? `type:${
|
|
6017
|
+
return type ? `type:${typeNode.text}*` : 'expr';
|
|
5425
6018
|
}
|
|
5426
6019
|
if (node.type === 'cast_expression') {
|
|
5427
6020
|
const typeNode = node.childForFieldName('type') ||
|
|
5428
6021
|
(node.namedChildren || []).find(child =>
|
|
5429
6022
|
TYPE_NODES.has(child.type) || child.type === 'type_descriptor');
|
|
5430
6023
|
const type = typeName(typeNode);
|
|
5431
|
-
return type ? `type:${
|
|
6024
|
+
return type ? `type:${typeNode.text}` : 'expr';
|
|
5432
6025
|
}
|
|
5433
6026
|
if (node.type === 'call_expression') {
|
|
5434
6027
|
const fnNode = node.childForFieldName('function');
|
|
@@ -5439,7 +6032,7 @@ function staticArgumentKind(node, variableTypes) {
|
|
|
5439
6032
|
(fnNode?.namedChildren || []).find(child =>
|
|
5440
6033
|
child.type === 'type_descriptor' || TYPE_NODES.has(child.type));
|
|
5441
6034
|
const type = typeName(typeNode);
|
|
5442
|
-
if (type) return `type:${
|
|
6035
|
+
if (type) return `type:${typeNode.text}`;
|
|
5443
6036
|
}
|
|
5444
6037
|
// Bare-identifier producers are name-resolvable (fix #299B): mark
|
|
5445
6038
|
// them `bcall:` so the overload discipline can type the argument
|
|
@@ -5458,13 +6051,34 @@ function staticArgumentKind(node, variableTypes) {
|
|
|
5458
6051
|
return 'expr';
|
|
5459
6052
|
}
|
|
5460
6053
|
|
|
6054
|
+
function argumentTypeContext(node) {
|
|
6055
|
+
const typeParameters = [];
|
|
6056
|
+
for (const [name, ranges] of templateScopes(node.tree)) {
|
|
6057
|
+
if (ranges.some(range => range.start <= node.startIndex && node.endIndex <= range.end)) typeParameters.push(name);
|
|
6058
|
+
}
|
|
6059
|
+
return { line: node.startPosition.row + 1, ...(typeParameters.length && { typeParameters }) };
|
|
6060
|
+
}
|
|
6061
|
+
|
|
6062
|
+
function argumentTypeOrigin(node, variableTypes) {
|
|
6063
|
+
if (node.type === 'parenthesized_expression') return argumentTypeOrigin(node.namedChild(0), variableTypes);
|
|
6064
|
+
if (node.type === 'identifier') return variableTypes?.getStaticOrigin(node.text, node) || null;
|
|
6065
|
+
return argumentTypeContext(node);
|
|
6066
|
+
}
|
|
6067
|
+
|
|
6068
|
+
function argumentFacts(values, variableTypes) {
|
|
6069
|
+
const argKinds = values.map(value => staticArgumentKind(value, variableTypes));
|
|
6070
|
+
const argTypeOrigins = values.map((value, i) => argKinds[i].startsWith('type:')
|
|
6071
|
+
? argumentTypeOrigin(value, variableTypes) : null);
|
|
6072
|
+
return { argKinds, ...(argTypeOrigins.some(Boolean) && { argTypeOrigins }) };
|
|
6073
|
+
}
|
|
6074
|
+
|
|
5461
6075
|
function callArguments(node, variableTypes) {
|
|
5462
6076
|
const args = node.childForFieldName('arguments');
|
|
5463
6077
|
if (!args) return { argCount: 0, argKinds: [] };
|
|
5464
6078
|
const values = args.namedChildren.filter(child => !child.type.endsWith('comment'));
|
|
5465
6079
|
return {
|
|
5466
6080
|
argCount: values.length,
|
|
5467
|
-
|
|
6081
|
+
...argumentFacts(values, variableTypes),
|
|
5468
6082
|
firstArg: values[0],
|
|
5469
6083
|
};
|
|
5470
6084
|
}
|
|
@@ -5485,8 +6099,11 @@ function templateScopes(tree) {
|
|
|
5485
6099
|
if (!parameter.type.endsWith('parameter_declaration') ||
|
|
5486
6100
|
parameter.type === 'parameter_declaration' ||
|
|
5487
6101
|
parameter.type === 'optional_parameter_declaration') continue;
|
|
5488
|
-
const
|
|
5489
|
-
|
|
6102
|
+
const namedParameter = parameter.type === 'template_template_parameter_declaration'
|
|
6103
|
+
? parameter.namedChildren.find(child => child.type.endsWith('type_parameter_declaration'))
|
|
6104
|
+
: parameter;
|
|
6105
|
+
const nameNode = namedParameter?.childForFieldName('name') ||
|
|
6106
|
+
(namedParameter?.namedChildren || []).find(child =>
|
|
5490
6107
|
child.type === 'type_identifier');
|
|
5491
6108
|
if (nameNode?.text) names.push(nameNode.text);
|
|
5492
6109
|
}
|
|
@@ -5558,12 +6175,12 @@ function callIdentity(fnNode) {
|
|
|
5558
6175
|
const globalQualified = fnNode.text.startsWith('::') &&
|
|
5559
6176
|
(!scopeNode || sameNode(scopeNode, rawNameNode));
|
|
5560
6177
|
if (globalQualified) {
|
|
6178
|
+
const nested = callIdentity(rawNameNode);
|
|
5561
6179
|
return {
|
|
5562
|
-
|
|
5563
|
-
nameNode: rawNameNode,
|
|
5564
|
-
isMethod: false,
|
|
6180
|
+
...nested,
|
|
5565
6181
|
isPathCall: true,
|
|
5566
|
-
|
|
6182
|
+
...(nested.receiver ? { receiver: `::${nested.receiver}` }
|
|
6183
|
+
: { isMethod: false, globalQualified: true }),
|
|
5567
6184
|
};
|
|
5568
6185
|
}
|
|
5569
6186
|
const nested = rawNameNode?.type === 'template_function' ||
|
|
@@ -5630,8 +6247,7 @@ function recoveredExplicitCallOperator(node, variableTypes) {
|
|
|
5630
6247
|
isMethod: false,
|
|
5631
6248
|
explicitTemplateCall: true,
|
|
5632
6249
|
argCount: values.length,
|
|
5633
|
-
|
|
5634
|
-
staticArgumentKind(value, variableTypes)),
|
|
6250
|
+
...argumentFacts(values, variableTypes),
|
|
5635
6251
|
enclosingFunction: enclosingFunctionOf(node),
|
|
5636
6252
|
};
|
|
5637
6253
|
}
|
|
@@ -5666,8 +6282,7 @@ function recoveredExplicitCallOperator(node, variableTypes) {
|
|
|
5666
6282
|
isMethod: false,
|
|
5667
6283
|
explicitTemplateCall: true,
|
|
5668
6284
|
argCount: values.length,
|
|
5669
|
-
|
|
5670
|
-
staticArgumentKind(value, variableTypes)),
|
|
6285
|
+
...argumentFacts(values, variableTypes),
|
|
5671
6286
|
enclosingFunction: enclosingFunctionOf(node),
|
|
5672
6287
|
};
|
|
5673
6288
|
}
|
|
@@ -5763,26 +6378,105 @@ function enclosingMacroArguments(node) {
|
|
|
5763
6378
|
* #401): the shape of `T v(X());` that is an object initialized by a call
|
|
5764
6379
|
* whenever X is not a type. null for any other parameter.
|
|
5765
6380
|
*/
|
|
5766
|
-
function
|
|
6381
|
+
function vexingParseCallIdentity(node) {
|
|
5767
6382
|
const list = node.parent;
|
|
5768
6383
|
const declarator = list?.type === 'parameter_list' ? list.parent : null;
|
|
5769
6384
|
const declaration = declarator?.type === 'function_declarator' ? declarator.parent : null;
|
|
5770
6385
|
if (declaration?.type !== 'declaration' || declaration.parent?.type !== 'compound_statement') return null;
|
|
5771
6386
|
const typeNode = node.childForFieldName('type');
|
|
5772
6387
|
const abstract = node.childForFieldName('declarator');
|
|
5773
|
-
if (
|
|
6388
|
+
if (!['type_identifier', 'template_type'].includes(typeNode?.type) ||
|
|
6389
|
+
abstract?.type !== 'abstract_function_declarator') return null;
|
|
5774
6390
|
const params = abstract.childForFieldName('parameters');
|
|
5775
6391
|
if (!params || params.namedChildCount !== 0) return null;
|
|
5776
6392
|
if (node.namedChildren.some(child => child.type === 'type_qualifier')) return null;
|
|
5777
|
-
|
|
6393
|
+
const identity = callIdentity(typeNode);
|
|
6394
|
+
// A template-template parameter is a type even when a project function
|
|
6395
|
+
// has the same name. Its empty function-type parameter is no call.
|
|
6396
|
+
if (typeNode.type === 'template_type' && enclosedByTemplateParameter(node, identity.name)) return null;
|
|
6397
|
+
return identity;
|
|
6398
|
+
}
|
|
6399
|
+
|
|
6400
|
+
function constructorFieldNames(owner) {
|
|
6401
|
+
const fields = new Set();
|
|
6402
|
+
const visit = body => {
|
|
6403
|
+
for (const field of body?.namedChildren || []) {
|
|
6404
|
+
if (field.type !== 'field_declaration' || functionDeclarator(field)) continue;
|
|
6405
|
+
const declarators = field.namedChildren.filter(child =>
|
|
6406
|
+
child.type === 'field_identifier' || child.type.endsWith('declarator'));
|
|
6407
|
+
for (const child of declarators) {
|
|
6408
|
+
const identity = declaratorIdentity(child);
|
|
6409
|
+
if (identity.name) fields.add(identity.name);
|
|
6410
|
+
}
|
|
6411
|
+
const aggregate = field.childForFieldName('type');
|
|
6412
|
+
if (!declarators.length && ['union_specifier', 'struct_specifier'].includes(aggregate?.type) &&
|
|
6413
|
+
!aggregate.childForFieldName('name')) visit(aggregate.childForFieldName('body'));
|
|
6414
|
+
}
|
|
6415
|
+
};
|
|
6416
|
+
visit(owner.childForFieldName('body'));
|
|
6417
|
+
return fields;
|
|
6418
|
+
}
|
|
6419
|
+
|
|
6420
|
+
function implicitConstructorInfo(node, ownerName, owner) {
|
|
6421
|
+
const declarator = functionDeclarator(node);
|
|
6422
|
+
if (!declarator || declaratorIdentity(declarator).name !== ownerName) return null;
|
|
6423
|
+
const name = declarator.childForFieldName('declarator');
|
|
6424
|
+
if (!name) return null;
|
|
6425
|
+
const { baseSpelling } = require('./constructor-bases');
|
|
6426
|
+
const fields = constructorFieldNames(owner);
|
|
6427
|
+
const initializers = node.namedChildren.find(child => child.type === 'field_initializer_list');
|
|
6428
|
+
const initialized = (initializers?.namedChildren || []).map(child => baseSpelling(child.namedChild(0), '::'))
|
|
6429
|
+
.filter(Boolean).map(entry => ({ ...entry, field: !entry.qualifier && fields.has(entry.name) }));
|
|
6430
|
+
const defaulted = node.namedChildren.some(child => child.type === 'default_method_clause');
|
|
6431
|
+
const parameters = parameterListOf(declarator);
|
|
6432
|
+
return { node, name, initialized,
|
|
6433
|
+
params: parameters ? nodeTextWithoutComments(parameters).replace(/^\(|\)$/g, '').trim() : null,
|
|
6434
|
+
body: node.childForFieldName('body') || (defaulted ? node : null),
|
|
6435
|
+
potential: defaulted,
|
|
6436
|
+
suppressed: node.namedChildren.some(child => child.type === 'delete_method_clause') ||
|
|
6437
|
+
defaulted && parameterListOf(declarator)?.namedChildCount > 0 ||
|
|
6438
|
+
initialized.some(entry => !entry.field && !entry.qualifier && entry.name === ownerName) };
|
|
6439
|
+
}
|
|
6440
|
+
|
|
6441
|
+
/**
|
|
6442
|
+
* The call a mem-initializer of an out-of-class constructor definition makes
|
|
6443
|
+
* (fix #421), or null when the definition is no constructor. Its own class
|
|
6444
|
+
* name (unqualified, or qualified as the definition qualifies the class)
|
|
6445
|
+
* delegates; any other name is a base or a member, decided by the engine.
|
|
6446
|
+
*/
|
|
6447
|
+
function outOfClassMemInitializer(definition, target, args, variableTypes) {
|
|
6448
|
+
const identity = declaratorIdentity(functionDeclarator(definition));
|
|
6449
|
+
const className = String(identity.className || '').replace(/<.*$/s, '').trim();
|
|
6450
|
+
if (!identity.name || identity.name !== className) return null;
|
|
6451
|
+
const name = typeName(target);
|
|
6452
|
+
if (!name) return null;
|
|
6453
|
+
const scope = target.childForFieldName('scope');
|
|
6454
|
+
const delegates = name === className && (!scope || scope.text.replace(/\s+/g, '') === identity.ownerQualifier);
|
|
6455
|
+
const values = args.namedChildren.filter(child => !child.type.endsWith('comment'));
|
|
6456
|
+
return { name, isMethod: false, isConstructor: true, constructorSyntax: 'delegation',
|
|
6457
|
+
ctorDelegation: delegates ? 'this' : 'super',
|
|
6458
|
+
...(scope && { constructorQualifier: scope.text }),
|
|
6459
|
+
...(!delegates && { memberInitializer: true }),
|
|
6460
|
+
argCount: values.length, ...argumentFacts(values, variableTypes) };
|
|
6461
|
+
}
|
|
6462
|
+
|
|
6463
|
+
function implicitOwnerScope(node) {
|
|
6464
|
+
const identity = classIdentity(node);
|
|
6465
|
+
const namespace = identity.ownerQualifier
|
|
6466
|
+
? [enclosingNamespace(node), identity.ownerQualifier].filter(Boolean).join('::') : enclosingNamespace(node);
|
|
6467
|
+
return { namespace, enclosingType: identity.enclosingType || enclosingClass(node)?.name || null,
|
|
6468
|
+
anonymousNamespace: classNamespaceScope(node, namespace).anonymous };
|
|
5778
6469
|
}
|
|
5779
6470
|
|
|
5780
6471
|
function findCallsInTree(code, parser, _options = {}, existingTree = null,
|
|
5781
|
-
includeMacroBodies = true) {
|
|
6472
|
+
includeMacroBodies = true, mode = 'cpp') {
|
|
5782
6473
|
const tree = existingTree || parseTree(parser, code);
|
|
5783
6474
|
const variableTypes = buildVariableTypes(tree);
|
|
5784
6475
|
const fieldTypes = variableTypes.fieldTypes;
|
|
5785
6476
|
const calls = [];
|
|
6477
|
+
const implicitConfig = require('./index').langTraits(mode)?.implicitBaseConstruction;
|
|
6478
|
+
const implicitOwners = implicitConfig ? getCachedNodeList(tree.rootNode).nodes
|
|
6479
|
+
.filter(node => implicitConfig.classes.includes(node.type)) : null;
|
|
5786
6480
|
traverseTreeCached(tree.rootNode, node => {
|
|
5787
6481
|
const recoveredOperator = recoveredExplicitCallOperator(
|
|
5788
6482
|
node, variableTypes);
|
|
@@ -5794,10 +6488,12 @@ function findCallsInTree(code, parser, _options = {}, existingTree = null,
|
|
|
5794
6488
|
// grammar reads a function declaration whose parameter is an
|
|
5795
6489
|
// unnamed function type; when `CurrentOptions` is no type (decided at
|
|
5796
6490
|
// query time) it is a call initializing the object.
|
|
5797
|
-
|
|
5798
|
-
|
|
6491
|
+
const declarationCall = node.type === 'parameter_declaration' ? vexingParseCallIdentity(node) : null;
|
|
6492
|
+
if (declarationCall) {
|
|
6493
|
+
const nameNode = declarationCall.nameNode;
|
|
5799
6494
|
calls.push({
|
|
5800
|
-
name:
|
|
6495
|
+
name: declarationCall.name,
|
|
6496
|
+
...(declarationCall.explicitTemplateCall && { explicitTemplateCall: true }),
|
|
5801
6497
|
line: nameNode.startPosition.row + 1,
|
|
5802
6498
|
column: nameNode.startPosition.column,
|
|
5803
6499
|
callStart: node.startIndex,
|
|
@@ -5911,6 +6607,15 @@ function findCallsInTree(code, parser, _options = {}, existingTree = null,
|
|
|
5911
6607
|
...(functionNode?.type === 'identifier' &&
|
|
5912
6608
|
cLocalBindingNames(tree).has(identity.name) &&
|
|
5913
6609
|
cBareNameShadowedByLocal(node, identity.name) && { localShadow: true }),
|
|
6610
|
+
// `(N)(x)` (fix #427): a call of N, or a cast to the type N;
|
|
6611
|
+
// which one depends on what N names where it is written.
|
|
6612
|
+
...(functionNode?.type === 'parenthesized_expression' && functionNode.namedChildCount === 1 &&
|
|
6613
|
+
['identifier', 'qualified_identifier'].includes(functionNode.namedChild(0).type) && {
|
|
6614
|
+
parenthesizedCallee: true,
|
|
6615
|
+
...(functionNode.namedChild(0).type === 'identifier' &&
|
|
6616
|
+
cLocalBindingNames(tree).has(identity.name) &&
|
|
6617
|
+
cBareNameShadowedByLocal(node, identity.name) && { localShadow: true }),
|
|
6618
|
+
}),
|
|
5914
6619
|
...(identity.receiver && { receiver: identity.receiver }),
|
|
5915
6620
|
...(identity.isPathCall && { isPathCall: true }),
|
|
5916
6621
|
...(identity.globalQualified && { globalQualified: true }),
|
|
@@ -5929,6 +6634,12 @@ function findCallsInTree(code, parser, _options = {}, existingTree = null,
|
|
|
5929
6634
|
// as an object records its declared type for that lookup.
|
|
5930
6635
|
...(identity.pointerAccess && receiverNode?.type === 'identifier' &&
|
|
5931
6636
|
receiverFields.length === 0 && { receiverArrow: true }),
|
|
6637
|
+
// The implicit object's constness where the declaration of
|
|
6638
|
+
// a plain variable receiver decides it (fix #427).
|
|
6639
|
+
...(receiverNode?.type === 'identifier' && receiverFields.length === 0 && !arrowThroughObject &&
|
|
6640
|
+
variableTypes.objectConst?.(receiverNode.text, node, identity.pointerAccess) !== undefined && {
|
|
6641
|
+
receiverConst: variableTypes.objectConst(receiverNode.text, node, identity.pointerAccess),
|
|
6642
|
+
}),
|
|
5932
6643
|
...(arrowThroughObject && variableTypes.get(receiverRoot, node) && {
|
|
5933
6644
|
receiverArrowObject: variableTypes.get(receiverRoot, node),
|
|
5934
6645
|
receiverArrowObjectAt: variableTypes.evidence(receiverRoot, node)
|
|
@@ -5963,10 +6674,14 @@ function findCallsInTree(code, parser, _options = {}, existingTree = null,
|
|
|
5963
6674
|
receiverField: receiverFields[0],
|
|
5964
6675
|
receiverFields,
|
|
5965
6676
|
...(receiverRootType && { receiverRootType }),
|
|
6677
|
+
// `field->m()` (fix #427): the member is looked up in
|
|
6678
|
+
// what `->` yields from the last field's declared type.
|
|
6679
|
+
...(identity.pointerAccess && { receiverFieldArrow: true }),
|
|
5966
6680
|
}),
|
|
5967
6681
|
...(assignedTo && { assignedTo }),
|
|
5968
6682
|
argCount: args.argCount,
|
|
5969
6683
|
argKinds: args.argKinds,
|
|
6684
|
+
...(args.argTypeOrigins && { argTypeOrigins: args.argTypeOrigins }),
|
|
5970
6685
|
enclosingFunction,
|
|
5971
6686
|
...(first && {
|
|
5972
6687
|
firstStringArg: first.value,
|
|
@@ -5983,7 +6698,10 @@ function findCallsInTree(code, parser, _options = {}, existingTree = null,
|
|
|
5983
6698
|
// parenthesized-value shape and recover its base/path identity.
|
|
5984
6699
|
const typeNode = node.childForFieldName('type');
|
|
5985
6700
|
const valueNode = node.childForFieldName('value');
|
|
5986
|
-
|
|
6701
|
+
// A declarator or a qualifier in the parentheses (`(BYTE*)(p)`,
|
|
6702
|
+
// `(const T)(x)`) only spells a type (fix #427).
|
|
6703
|
+
if (typeNode?.type === 'type_descriptor' && !typeNode.childForFieldName('declarator') &&
|
|
6704
|
+
!(typeNode.namedChildren || []).some(child => child.type === 'type_qualifier') &&
|
|
5987
6705
|
valueNode?.type === 'parenthesized_expression') {
|
|
5988
6706
|
const identity = callIdentity(typeNode);
|
|
5989
6707
|
if (identity.name) {
|
|
@@ -5997,15 +6715,50 @@ function findCallsInTree(code, parser, _options = {}, existingTree = null,
|
|
|
5997
6715
|
isMethod: identity.isMethod,
|
|
5998
6716
|
...(identity.receiver && { receiver: identity.receiver }),
|
|
5999
6717
|
...(identity.isPathCall && { isPathCall: true }),
|
|
6718
|
+
parenthesizedCallee: true,
|
|
6000
6719
|
argCount: values.length,
|
|
6001
|
-
|
|
6002
|
-
staticArgumentKind(value, variableTypes)),
|
|
6720
|
+
...argumentFacts(values, variableTypes),
|
|
6003
6721
|
enclosingFunction: enclosingFunctionOf(node),
|
|
6004
6722
|
});
|
|
6005
6723
|
return false;
|
|
6006
6724
|
}
|
|
6007
6725
|
}
|
|
6008
6726
|
}
|
|
6727
|
+
if (mode === 'cpp' && node.type === 'field_initializer') {
|
|
6728
|
+
let owner = node.parent, definition = null;
|
|
6729
|
+
while (owner && !['class_specifier', 'struct_specifier', 'union_specifier'].includes(owner.type)) {
|
|
6730
|
+
if (!definition && owner.type === 'function_definition') definition = owner;
|
|
6731
|
+
owner = owner.parent;
|
|
6732
|
+
}
|
|
6733
|
+
const target = node.namedChild(0), args = node.namedChildren.find(child => child.type === 'argument_list');
|
|
6734
|
+
if (!owner && definition && target && args) {
|
|
6735
|
+
// An out-of-class constructor definition (`W::W(..) : W(..)`,
|
|
6736
|
+
// `D::D() : B(..)`, fix #421): its class body is in another
|
|
6737
|
+
// place, so a name other than the class's own may be a
|
|
6738
|
+
// member. The engine decides that by the class at query time.
|
|
6739
|
+
const outOfClass = outOfClassMemInitializer(definition, target, args, variableTypes);
|
|
6740
|
+
if (outOfClass) calls.push({ ...outOfClass, line: target.startPosition.row + 1,
|
|
6741
|
+
column: target.startPosition.column, callStart: node.startIndex, callEnd: node.endIndex,
|
|
6742
|
+
enclosingFunction: enclosingFunctionOf(node) });
|
|
6743
|
+
}
|
|
6744
|
+
if (owner && target && args) {
|
|
6745
|
+
const hidden = constructorFieldNames(owner).has(target.text);
|
|
6746
|
+
const self = owner.childForFieldName('name');
|
|
6747
|
+
const bases = owner.namedChildren.find(child => child.type === 'base_class_clause')?.namedChildren || [];
|
|
6748
|
+
const written = [self, ...bases].filter(Boolean).find(type => typeName(type) === typeName(target));
|
|
6749
|
+
if (!hidden && written) {
|
|
6750
|
+
const values = args.namedChildren.filter(child => !child.type.endsWith('comment'));
|
|
6751
|
+
const scope = target.childForFieldName('scope') || written.childForFieldName('scope');
|
|
6752
|
+
calls.push({ name: typeName(target), line: target.startPosition.row + 1,
|
|
6753
|
+
column: target.startPosition.column, callStart: node.startIndex, callEnd: node.endIndex,
|
|
6754
|
+
isMethod: false, isConstructor: true, constructorSyntax: 'delegation',
|
|
6755
|
+
ctorDelegation: sameNode(written, self) ? 'this' : 'super',
|
|
6756
|
+
...(scope && { constructorQualifier: scope.text }),
|
|
6757
|
+
argCount: values.length, ...argumentFacts(values, variableTypes),
|
|
6758
|
+
enclosingFunction: enclosingFunctionOf(node) });
|
|
6759
|
+
}
|
|
6760
|
+
}
|
|
6761
|
+
}
|
|
6009
6762
|
if (node.type === 'new_expression') {
|
|
6010
6763
|
const typeNode = node.childForFieldName('type') ||
|
|
6011
6764
|
node.namedChildren.find(child => child.type === 'type_identifier' ||
|
|
@@ -6020,18 +6773,87 @@ function findCallsInTree(code, parser, _options = {}, existingTree = null,
|
|
|
6020
6773
|
column: typeNode.startPosition.column,
|
|
6021
6774
|
isMethod: false,
|
|
6022
6775
|
isConstructor: true,
|
|
6776
|
+
...(mode === 'cpp' && node.childForFieldName('arguments')?.type === 'initializer_list' && {
|
|
6777
|
+
constructorSyntax: 'list',
|
|
6778
|
+
}),
|
|
6779
|
+
...(typeNode.childForFieldName('scope') && {
|
|
6780
|
+
constructorQualifier: typeNode.childForFieldName('scope').text,
|
|
6781
|
+
}),
|
|
6023
6782
|
argCount: args.argCount,
|
|
6024
6783
|
argKinds: args.argKinds,
|
|
6784
|
+
...(args.argTypeOrigins && { argTypeOrigins: args.argTypeOrigins }),
|
|
6025
6785
|
enclosingFunction: enclosingFunctionOf(node),
|
|
6026
6786
|
});
|
|
6027
6787
|
}
|
|
6028
6788
|
}
|
|
6789
|
+
if (mode === 'cpp' && node.type === 'init_declarator' && node.parent?.type === 'declaration') {
|
|
6790
|
+
const declared = node.childForFieldName('declarator');
|
|
6791
|
+
const value = node.childForFieldName('value');
|
|
6792
|
+
const type = node.parent.childForFieldName('type');
|
|
6793
|
+
if (declared?.type === 'identifier' && ['argument_list', 'initializer_list'].includes(value?.type) &&
|
|
6794
|
+
['type_identifier', 'qualified_identifier', 'template_type'].includes(type?.type)) {
|
|
6795
|
+
const name = typeName(type);
|
|
6796
|
+
const values = value.namedChildren.filter(child => !child.type.endsWith('comment'));
|
|
6797
|
+
if (name) calls.push({
|
|
6798
|
+
name, line: declared.startPosition.row + 1, column: declared.startPosition.column,
|
|
6799
|
+
callStart: node.startIndex, callEnd: node.endIndex,
|
|
6800
|
+
isMethod: false, isConstructor: true,
|
|
6801
|
+
constructorSyntax: value.type === 'initializer_list' ? 'list' : 'direct',
|
|
6802
|
+
...(type.childForFieldName('scope') && { constructorQualifier: type.childForFieldName('scope').text }),
|
|
6803
|
+
argCount: values.length, ...argumentFacts(values, variableTypes),
|
|
6804
|
+
enclosingFunction: enclosingFunctionOf(node),
|
|
6805
|
+
});
|
|
6806
|
+
}
|
|
6807
|
+
}
|
|
6808
|
+
// `Box b;`, `static Box s;`, `Box a[2];` (fix #421): default
|
|
6809
|
+
// initialization runs the default constructor of a class type. The
|
|
6810
|
+
// engine keeps the record only where the name denotes a class.
|
|
6811
|
+
if (mode === 'cpp' && node.type === 'declaration') {
|
|
6812
|
+
const type = node.childForFieldName('type');
|
|
6813
|
+
const external = node.namedChildren.some(child => child.type === 'storage_class_specifier' && child.text === 'extern');
|
|
6814
|
+
const name = !external && ['type_identifier', 'qualified_identifier', 'template_type'].includes(type?.type)
|
|
6815
|
+
? typeName(type) : null;
|
|
6816
|
+
for (const declarator of name ? node.childrenForFieldName('declarator') : []) {
|
|
6817
|
+
const target = declarator.type === 'array_declarator' ? declarator.childForFieldName('declarator') : declarator;
|
|
6818
|
+
if (target?.type !== 'identifier') continue;
|
|
6819
|
+
calls.push({ name, line: target.startPosition.row + 1, column: target.startPosition.column,
|
|
6820
|
+
callStart: declarator.startIndex, callEnd: declarator.endIndex,
|
|
6821
|
+
isMethod: false, isConstructor: true, constructorSyntax: 'default',
|
|
6822
|
+
...(type.childForFieldName('scope') && { constructorQualifier: type.childForFieldName('scope').text }),
|
|
6823
|
+
argCount: 0, argKinds: [], enclosingFunction: enclosingFunctionOf(node) });
|
|
6824
|
+
}
|
|
6825
|
+
}
|
|
6826
|
+
if (mode === 'cpp' && node.type === 'compound_literal_expression') {
|
|
6827
|
+
const type = node.childForFieldName('type'), value = node.childForFieldName('value');
|
|
6828
|
+
const name = type && typeName(type);
|
|
6829
|
+
if (name && value?.type === 'initializer_list') {
|
|
6830
|
+
const values = value.namedChildren.filter(child => !child.type.endsWith('comment'));
|
|
6831
|
+
calls.push({ name, line: type.startPosition.row + 1, column: type.startPosition.column,
|
|
6832
|
+
callStart: node.startIndex, callEnd: node.endIndex,
|
|
6833
|
+
isMethod: false, isConstructor: true, constructorSyntax: 'list',
|
|
6834
|
+
...(type.childForFieldName('scope') && { constructorQualifier: type.childForFieldName('scope').text }),
|
|
6835
|
+
argCount: values.length, ...argumentFacts(values, variableTypes),
|
|
6836
|
+
enclosingFunction: enclosingFunctionOf(node) });
|
|
6837
|
+
}
|
|
6838
|
+
}
|
|
6029
6839
|
return true;
|
|
6030
6840
|
});
|
|
6031
6841
|
if (includeMacroBodies) calls.push(...findMacroBodyCalls(tree, code, parser));
|
|
6842
|
+
calls.push(...require('./constructor-bases').implicitBaseCalls(tree.rootNode, mode, null, implicitOwners));
|
|
6032
6843
|
return calls;
|
|
6033
6844
|
}
|
|
6034
6845
|
|
|
6846
|
+
/** Omitted base initializer calls of one class, read as findCallsInCode reads them (fix #421). */
|
|
6847
|
+
function implicitBaseCallsOf(code, parser, ownerLine, ownerEndLine, mode) {
|
|
6848
|
+
const { implicitBaseCallsOfOwner } = require('./constructor-bases');
|
|
6849
|
+
const tree = parseTree(parser, code);
|
|
6850
|
+
const primary = implicitBaseCallsOfOwner(tree.rootNode, mode, null, ownerLine, ownerEndLine);
|
|
6851
|
+
const literal = literalRecoveryTree(parser, code, tree);
|
|
6852
|
+
if (!literal) return primary;
|
|
6853
|
+
return mergeExtracted(primary, implicitBaseCallsOfOwner(literal.rootNode, mode, null, ownerLine, ownerEndLine)
|
|
6854
|
+
.map(call => ({ ...call, configurationVariant: true })), callIdentityKey);
|
|
6855
|
+
}
|
|
6856
|
+
|
|
6035
6857
|
function callIdentityKey(call) {
|
|
6036
6858
|
return [
|
|
6037
6859
|
call.name,
|
|
@@ -6126,6 +6948,36 @@ function directiveRanges(code) {
|
|
|
6126
6948
|
return ranges;
|
|
6127
6949
|
}
|
|
6128
6950
|
|
|
6951
|
+
/**
|
|
6952
|
+
* A namespace-scope function declared `static` keeps internal linkage in
|
|
6953
|
+
* every later declaration of the file that omits the storage class, its
|
|
6954
|
+
* definition included (C11 6.2.2p4-5; C++ [dcl.stc]: successive
|
|
6955
|
+
* declarations of an entity agree on linkage). Those declarations are
|
|
6956
|
+
* recorded `static` (fix #427). A `static` declaration in a conditional
|
|
6957
|
+
* branch reaches only the later declarations inside that branch.
|
|
6958
|
+
*/
|
|
6959
|
+
function inheritDeclaredLinkage(functions, mode) {
|
|
6960
|
+
const paramKey = fn => (fn.paramsStructured || []).map(param => param.variadic ? '...'
|
|
6961
|
+
: String(param.type || (param.unnamed ? param.name : '') || '').replace(/\s+/g, '')).join(',');
|
|
6962
|
+
const keyOf = fn => mode === 'cpp' ? `${fn.namespace || ''}\0${fn.name}\0${paramKey(fn)}` : fn.name;
|
|
6963
|
+
const free = fn => !fn.className && !fn.receiver && !fn.isMethod && !fn.friendOf &&
|
|
6964
|
+
!fn.lexicalScopeStartLine && Array.isArray(fn.modifiers);
|
|
6965
|
+
const internal = new Map();
|
|
6966
|
+
const ordered = functions.filter(free).sort((a, b) => a.startLine - b.startLine);
|
|
6967
|
+
for (const fn of ordered) {
|
|
6968
|
+
const key = keyOf(fn);
|
|
6969
|
+
if (fn.modifiers.includes('static')) {
|
|
6970
|
+
if (!internal.has(key)) internal.set(key, []);
|
|
6971
|
+
internal.get(key).push(fn);
|
|
6972
|
+
continue;
|
|
6973
|
+
}
|
|
6974
|
+
const reaches = (internal.get(key) || []).some(prior => prior.startLine < fn.startLine &&
|
|
6975
|
+
(!prior.ppBranch || (fn.startLine >= prior.ppBranch[0] && fn.startLine <= prior.ppBranch[1])));
|
|
6976
|
+
if (!reaches) continue;
|
|
6977
|
+
fn.modifiers = [...fn.modifiers.filter(modifier => modifier !== 'export'), 'static'];
|
|
6978
|
+
}
|
|
6979
|
+
}
|
|
6980
|
+
|
|
6129
6981
|
function attributeCallsToLexicalFunctions(calls, functions) {
|
|
6130
6982
|
const bodies = functions.filter(fn => !fn.isSignature)
|
|
6131
6983
|
.sort((a, b) =>
|
|
@@ -6133,7 +6985,8 @@ function attributeCallsToLexicalFunctions(calls, functions) {
|
|
|
6133
6985
|
b.startLine - a.startLine);
|
|
6134
6986
|
for (const call of calls) {
|
|
6135
6987
|
const owner = bodies.find(fn =>
|
|
6136
|
-
fn.startLine <= call.line && call.line <= fn.endLine
|
|
6988
|
+
fn.startLine <= call.line && call.line <= fn.endLine &&
|
|
6989
|
+
require('../core/callable-scope').containsCall(fn, call) !== false);
|
|
6137
6990
|
if (!owner) continue;
|
|
6138
6991
|
call.enclosingFunction = {
|
|
6139
6992
|
name: owner.name,
|
|
@@ -6301,13 +7154,13 @@ function findCallsInCode(code, parser, options = {}, existingTree = null,
|
|
|
6301
7154
|
// in preprocessor-configuration conservation.
|
|
6302
7155
|
if (existingTree) {
|
|
6303
7156
|
const calls = findCallsInTree(
|
|
6304
|
-
code, parser, options, existingTree, includeMacroBodies);
|
|
7157
|
+
code, parser, options, existingTree, includeMacroBodies, mode);
|
|
6305
7158
|
const functions = findFunctionsInTree(code, existingTree, mode);
|
|
6306
7159
|
return markDirectiveBodyCalls(code, attributeCallsToLexicalFunctions(calls, functions));
|
|
6307
7160
|
}
|
|
6308
7161
|
const tree = parseTree(parser, code);
|
|
6309
7162
|
const primary = findCallsInTree(
|
|
6310
|
-
code, parser, options, tree, includeMacroBodies);
|
|
7163
|
+
code, parser, options, tree, includeMacroBodies, mode);
|
|
6311
7164
|
const literal = literalRecoveryTree(parser, code, tree);
|
|
6312
7165
|
if (!literal) {
|
|
6313
7166
|
return markDirectiveBodyCalls(code, attributeCallsToLexicalFunctions(
|
|
@@ -6315,7 +7168,7 @@ function findCallsInCode(code, parser, options = {}, existingTree = null,
|
|
|
6315
7168
|
}
|
|
6316
7169
|
try {
|
|
6317
7170
|
const literalCalls = findCallsInTree(
|
|
6318
|
-
code, parser, options, literal, includeMacroBodies)
|
|
7171
|
+
code, parser, options, literal, includeMacroBodies, mode)
|
|
6319
7172
|
.map(call => ({ ...call, configurationVariant: true }));
|
|
6320
7173
|
// Selected-configuration facts retain their stronger evidence. Only
|
|
6321
7174
|
// literal-only sites carry configurationVariant and are therefore
|
|
@@ -6343,6 +7196,32 @@ function findCallsInCode(code, parser, options = {}, existingTree = null,
|
|
|
6343
7196
|
* for conservation but marked as lexical parameter dispatch; they must never
|
|
6344
7197
|
* resolve to an unrelated project function that happens to share `fn`'s name.
|
|
6345
7198
|
*/
|
|
7199
|
+
/**
|
|
7200
|
+
* A replacement list with its stringizing operators read as the string
|
|
7201
|
+
* literals they produce, every byte kept in place (fix #430):
|
|
7202
|
+
* `AssertPred1Helper(#pred, #v1, pred, v1)` is a call of four arguments, not
|
|
7203
|
+
* a parse error of three. Token pasting stays as written: the pasted
|
|
7204
|
+
* parameter is what the macro's parameter effects read.
|
|
7205
|
+
*/
|
|
7206
|
+
function withoutPreprocessingOperators(body, parameters) {
|
|
7207
|
+
if (!body.includes('#')) return body;
|
|
7208
|
+
const tokens = lexPP(body);
|
|
7209
|
+
const chars = body.split('');
|
|
7210
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
7211
|
+
const token = tokens[i];
|
|
7212
|
+
if (token.k !== 'punct') continue;
|
|
7213
|
+
if (token.v === '#' && tokens[i + 1]?.k === 'id' &&
|
|
7214
|
+
(parameters.has(tokens[i + 1].v) || tokens[i + 1].v === '__VA_ARGS__')) {
|
|
7215
|
+
const end = tokens[i + 1].start + tokens[i + 1].v.length;
|
|
7216
|
+
for (let at = token.start; at < end; at++) chars[at] = 'x';
|
|
7217
|
+
chars[token.start] = '"';
|
|
7218
|
+
chars[end - 1] = '"';
|
|
7219
|
+
i++;
|
|
7220
|
+
}
|
|
7221
|
+
}
|
|
7222
|
+
return chars.join('');
|
|
7223
|
+
}
|
|
7224
|
+
|
|
6346
7225
|
function findMacroBodyCalls(tree, code, parser, onlyName = null) {
|
|
6347
7226
|
const calls = [];
|
|
6348
7227
|
for (const node of macroDefinitionNodes(tree)) {
|
|
@@ -6363,7 +7242,7 @@ function findMacroBodyCalls(tree, code, parser, onlyName = null) {
|
|
|
6363
7242
|
const parameters = new Set(parameterNames);
|
|
6364
7243
|
// Replace only the continuation backslash. Keeping the newline and
|
|
6365
7244
|
// every other byte makes source-line/column and span remapping exact.
|
|
6366
|
-
const body = valueNode.text.replace(/\\(?=\r?\n)/g, ' ');
|
|
7245
|
+
const body = withoutPreprocessingOperators(valueNode.text.replace(/\\(?=\r?\n)/g, ' '), parameters);
|
|
6367
7246
|
const prefix = 'void __ucn_macro_body__(void) {\n';
|
|
6368
7247
|
const synthetic = `${prefix}${body}\n;}`;
|
|
6369
7248
|
// Synthetic replacement-list wrappers are one-shot parse inputs. Do
|
|
@@ -6408,6 +7287,7 @@ function findMacroBodyCalls(tree, code, parser, onlyName = null) {
|
|
|
6408
7287
|
: valueNode.startIndex + call.callEnd - prefix.length;
|
|
6409
7288
|
calls.push({
|
|
6410
7289
|
...withoutParameterReceiverTypes(call, parameters),
|
|
7290
|
+
...(call.argTypeOrigins && { argTypeOrigins: call.argTypeOrigins.map(() => null) }),
|
|
6411
7291
|
line,
|
|
6412
7292
|
column,
|
|
6413
7293
|
...(callStart != null && callStart >= valueNode.startIndex && {
|
|
@@ -6494,7 +7374,77 @@ function findImportsInCode(code, parser) {
|
|
|
6494
7374
|
} finally { /* cached with the selected tree */ }
|
|
6495
7375
|
}
|
|
6496
7376
|
|
|
6497
|
-
|
|
7377
|
+
// Follow only the terminal name of a template, qualifier or member access.
|
|
7378
|
+
// Template arguments and receiver tokens keep their own expression roles.
|
|
7379
|
+
function usageNameExpression(node) {
|
|
7380
|
+
let current = node;
|
|
7381
|
+
while (current.parent) {
|
|
7382
|
+
const parent = current.parent;
|
|
7383
|
+
let head;
|
|
7384
|
+
switch (parent.type) {
|
|
7385
|
+
case 'template_function': case 'template_method': case 'template_type':
|
|
7386
|
+
case 'qualified_identifier':
|
|
7387
|
+
head = parent.childForFieldName('name');
|
|
7388
|
+
break;
|
|
7389
|
+
case 'field_expression':
|
|
7390
|
+
head = parent.childForFieldName('field');
|
|
7391
|
+
break;
|
|
7392
|
+
case 'dependent_name': case 'parenthesized_expression':
|
|
7393
|
+
head = parent.namedChildCount === 1 ? parent.namedChild(0) : null;
|
|
7394
|
+
break;
|
|
7395
|
+
default:
|
|
7396
|
+
return current;
|
|
7397
|
+
}
|
|
7398
|
+
if (!sameNode(head, current)) return current;
|
|
7399
|
+
current = parent;
|
|
7400
|
+
}
|
|
7401
|
+
return current;
|
|
7402
|
+
}
|
|
7403
|
+
|
|
7404
|
+
function invokesNameExpression(expression) {
|
|
7405
|
+
return expression.parent?.type === 'call_expression' &&
|
|
7406
|
+
sameNode(expression.parent.childForFieldName('function'), expression);
|
|
7407
|
+
}
|
|
7408
|
+
|
|
7409
|
+
function isInvokedName(node, options = {}) {
|
|
7410
|
+
const expression = usageNameExpression(node);
|
|
7411
|
+
if (!invokesNameExpression(expression)) return false;
|
|
7412
|
+
if (options.unqualified) {
|
|
7413
|
+
const identity = callIdentity(expression);
|
|
7414
|
+
if (identity.isMethod || identity.isPathCall) return false;
|
|
7415
|
+
}
|
|
7416
|
+
return true;
|
|
7417
|
+
}
|
|
7418
|
+
|
|
7419
|
+
// The name introduced by a declarator, including `A::f`, pointer/array
|
|
7420
|
+
// wrappers and data members. Keep this separate from the public usage kind:
|
|
7421
|
+
// an explicit instantiation still references a function the index declares
|
|
7422
|
+
// elsewhere. Refactoring uses the flag only on indexed declaration lines.
|
|
7423
|
+
function isDeclarationNameToken(node) {
|
|
7424
|
+
let current = node;
|
|
7425
|
+
for (let depth = 0; current?.parent && depth < 16; depth++) {
|
|
7426
|
+
const parent = current.parent;
|
|
7427
|
+
if (parent.type === 'qualified_identifier') {
|
|
7428
|
+
if (!sameNode(parent.childForFieldName('name'), current)) return false;
|
|
7429
|
+
} else if (parent.type.endsWith('_declarator')) {
|
|
7430
|
+
const declarator = parent.childForFieldName('declarator') ||
|
|
7431
|
+
(parent.type === 'parenthesized_declarator' && parent.namedChildCount === 1
|
|
7432
|
+
? parent.namedChild(0) : null);
|
|
7433
|
+
if (!sameNode(declarator, current)) return false;
|
|
7434
|
+
if (parent.type === 'function_declarator') return true;
|
|
7435
|
+
} else {
|
|
7436
|
+
if (['declaration', 'field_declaration', 'function_definition', 'type_definition',
|
|
7437
|
+
'parameter_declaration', 'optional_parameter_declaration', 'alias_declaration',
|
|
7438
|
+
'enumerator', 'namespace_definition', 'namespace_alias_definition'].includes(parent.type)) {
|
|
7439
|
+
return sameNode(parent.childForFieldName('name'), current) ||
|
|
7440
|
+
parent.childrenForFieldName('declarator').some(declarator => sameNode(declarator, current));
|
|
7441
|
+
}
|
|
7442
|
+
return false;
|
|
7443
|
+
}
|
|
7444
|
+
current = parent;
|
|
7445
|
+
}
|
|
7446
|
+
return false;
|
|
7447
|
+
}
|
|
6498
7448
|
|
|
6499
7449
|
function findUsagesInCode(code, name, parser, existingTree, options = {}) {
|
|
6500
7450
|
// Usage is the raw literal-name inventory. The literal C/C++ tree retains
|
|
@@ -6506,6 +7456,9 @@ function findUsagesInCode(code, name, parser, existingTree, options = {}) {
|
|
|
6506
7456
|
safeParse(parser, code, undefined, PARSE_OPTIONS);
|
|
6507
7457
|
const usages = [];
|
|
6508
7458
|
const scopeMemo = options.lexicalScopes && options.mode === 'c' ? new Map() : null;
|
|
7459
|
+
// Occurrence accounts consume positions and kinds, never receiver facts.
|
|
7460
|
+
// Keep the same invocation classifier while omitting unrelated ownership.
|
|
7461
|
+
const kindsOnly = options.usageKindsOnly && !options.lexicalScopes;
|
|
6509
7462
|
const seenUsages = new Set();
|
|
6510
7463
|
const addUsage = usage => {
|
|
6511
7464
|
const key = `${usage.line}:${usage.column ?? ''}:${usage.usageType}:${usage.receiver || ''}`;
|
|
@@ -6519,27 +7472,10 @@ function findUsagesInCode(code, name, parser, existingTree, options = {}) {
|
|
|
6519
7472
|
let receiver = null;
|
|
6520
7473
|
const parent = node.parent;
|
|
6521
7474
|
if (parent) {
|
|
6522
|
-
|
|
6523
|
-
|
|
6524
|
-
|
|
6525
|
-
|
|
6526
|
-
// levels below the call (fix #378).
|
|
6527
|
-
if (!call && CALLEE_WRAPPER_NODES.has(parent.type)) {
|
|
6528
|
-
let up = parent.parent;
|
|
6529
|
-
for (let depth = 0; up && depth < 3 && CALLEE_WRAPPER_NODES.has(up.type); depth++) {
|
|
6530
|
-
up = up.parent;
|
|
6531
|
-
}
|
|
6532
|
-
if (up?.type === 'call_expression') call = up;
|
|
6533
|
-
}
|
|
6534
|
-
// Only the callable's terminal name is a call usage. The previous
|
|
6535
|
-
// descendant test also matched receiver identifiers, classifying
|
|
6536
|
-
// `copy.descriptor()` as a call to a free function named `copy`.
|
|
6537
|
-
// callIdentity already unwraps qualified/template/parenthesized
|
|
6538
|
-
// callees while preserving the exact terminal name node.
|
|
6539
|
-
const calledIdentity = call
|
|
6540
|
-
? callIdentity(call.childForFieldName('function')) : null;
|
|
6541
|
-
if (calledIdentity?.nameNode &&
|
|
6542
|
-
sameNode(calledIdentity.nameNode, node)) {
|
|
7475
|
+
const expression = usageNameExpression(node);
|
|
7476
|
+
const invoked = invokesNameExpression(expression);
|
|
7477
|
+
const identity = kindsOnly && !invoked ? null : callIdentity(expression);
|
|
7478
|
+
if (invoked && sameNode(identity.nameNode, node)) {
|
|
6543
7479
|
usageType = 'call';
|
|
6544
7480
|
} else if ((parent.type === 'function_declarator' ||
|
|
6545
7481
|
parent.type === 'parameter_declaration') &&
|
|
@@ -6562,22 +7498,14 @@ function findUsagesInCode(code, name, parser, existingTree, options = {}) {
|
|
|
6562
7498
|
} else if (parent.type === 'preproc_include') {
|
|
6563
7499
|
usageType = 'import';
|
|
6564
7500
|
}
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
receiver = typeName(parent.childForFieldName('scope') ||
|
|
6568
|
-
parent.namedChild(0));
|
|
6569
|
-
} else if (parent.type === 'field_expression' &&
|
|
6570
|
-
sameNode(parent.childForFieldName('field'), node)) {
|
|
6571
|
-
const argument = parent.childForFieldName('argument') ||
|
|
6572
|
-
parent.namedChild(0);
|
|
6573
|
-
receiver = argument?.text || null;
|
|
6574
|
-
}
|
|
7501
|
+
// A generic wrapper does not erase an explicit receiver.
|
|
7502
|
+
if (!kindsOnly && sameNode(identity.nameNode, node)) receiver = identity.receiver || null;
|
|
6575
7503
|
}
|
|
6576
7504
|
// A bare name lexically inside a class method participates in C++
|
|
6577
7505
|
// member lookup on that class. This is ownership evidence for test
|
|
6578
7506
|
// discovery and usage presentation; receiver-qualified forms above
|
|
6579
7507
|
// retain their explicit receiver instead.
|
|
6580
|
-
if (!receiver) receiver = enclosingClassName(node);
|
|
7508
|
+
if (!kindsOnly && !receiver) receiver = enclosingClassName(node);
|
|
6581
7509
|
// Where a bare C reference resolves (fix #396): block declarations,
|
|
6582
7510
|
// parameters and `for` initializers bind it, else file scope.
|
|
6583
7511
|
const scope = scopeMemo && usageType === 'reference' && !receiver
|
|
@@ -6586,6 +7514,8 @@ function findUsagesInCode(code, name, parser, existingTree, options = {}) {
|
|
|
6586
7514
|
line: node.startPosition.row + 1,
|
|
6587
7515
|
column: node.startPosition.column,
|
|
6588
7516
|
usageType,
|
|
7517
|
+
...(options.lexicalScopes && usageType === 'reference' &&
|
|
7518
|
+
isDeclarationNameToken(node) && { declarationName: true }),
|
|
6589
7519
|
...(receiver && { receiver }),
|
|
6590
7520
|
...scope,
|
|
6591
7521
|
});
|
|
@@ -6601,7 +7531,7 @@ function findUsagesInCode(code, name, parser, existingTree, options = {}) {
|
|
|
6601
7531
|
.map(usage => `${usage.line}:${usage.column ?? ''}`));
|
|
6602
7532
|
const macroCalls = findMacroBodyCalls(tree, code, parser, name);
|
|
6603
7533
|
for (const call of macroCalls) {
|
|
6604
|
-
if (call.name !== name) continue;
|
|
7534
|
+
if (call.name !== name || call.constructorSyntax === 'implicit') continue;
|
|
6605
7535
|
const key = `${call.line}:${call.column ?? ''}`;
|
|
6606
7536
|
if (seenCalls.has(key)) continue;
|
|
6607
7537
|
seenCalls.add(key);
|
|
@@ -6635,7 +7565,8 @@ function parse(code, parser, mode, options = {}) {
|
|
|
6635
7565
|
if (options.externalMacros && !externalMacroContext) {
|
|
6636
7566
|
return withExternalMacros(options.externalMacros, () => parse(code, parser, mode, options));
|
|
6637
7567
|
}
|
|
6638
|
-
const
|
|
7568
|
+
const rawCapture = mode === 'cpp' ? { source: code, tree: null } : null;
|
|
7569
|
+
const tree = parseTree(parser, code, rawCapture);
|
|
6639
7570
|
const literal = literalRecoveryTree(parser, code, tree);
|
|
6640
7571
|
try {
|
|
6641
7572
|
const lines = code.split('\n');
|
|
@@ -6664,11 +7595,11 @@ function parse(code, parser, mode, options = {}) {
|
|
|
6664
7595
|
item => `${item.module}:${item.line}:${item.type}`,
|
|
6665
7596
|
)
|
|
6666
7597
|
: findImportsInTree(code, tree);
|
|
6667
|
-
const primaryCalls = findCallsInTree(code, parser, {}, tree, true);
|
|
7598
|
+
const primaryCalls = findCallsInTree(code, parser, {}, tree, true, mode);
|
|
6668
7599
|
const calls = literal
|
|
6669
7600
|
? mergeExtracted(
|
|
6670
7601
|
primaryCalls,
|
|
6671
|
-
findCallsInTree(code, parser, {}, literal, true)
|
|
7602
|
+
findCallsInTree(code, parser, {}, literal, true, mode)
|
|
6672
7603
|
.map(call => ({ ...call, configurationVariant: true })),
|
|
6673
7604
|
callIdentityKey,
|
|
6674
7605
|
)
|
|
@@ -6689,6 +7620,7 @@ function parse(code, parser, mode, options = {}) {
|
|
|
6689
7620
|
classes.sort(byLine);
|
|
6690
7621
|
calls.sort(byLine);
|
|
6691
7622
|
}
|
|
7623
|
+
inheritDeclaredLinkage(functions, mode);
|
|
6692
7624
|
attributeCallsToLexicalFunctions(calls, functions);
|
|
6693
7625
|
markDirectiveBodyCalls(code, calls);
|
|
6694
7626
|
const result = {
|
|
@@ -6728,9 +7660,20 @@ function parse(code, parser, mode, options = {}) {
|
|
|
6728
7660
|
result.externalMacroNames = [...externalMacroContext.consulted].sort();
|
|
6729
7661
|
}
|
|
6730
7662
|
if (mode === 'cpp') {
|
|
7663
|
+
result.cppBaseSyntax = require('./cpp-site-syntax').cppBaseSyntax(code, parser,
|
|
7664
|
+
calls.filter(call => call.implicitBase?.header).map(call => call.implicitBase.header),
|
|
7665
|
+
rawCapture.tree || (selectedSourceByTree.get(tree) === code ? tree : null));
|
|
7666
|
+
result.typeParameterNames = require('./utils').declaredTypeParameterNames(tree, literal);
|
|
6731
7667
|
const markers = findMacroScopeMarkers(code, tree, lines);
|
|
6732
7668
|
if (markers.length > 0) result.macroScopeMarkers = markers;
|
|
6733
|
-
const
|
|
7669
|
+
const primaryUsings = findCppUsingFacts(tree);
|
|
7670
|
+
const originalUsings = literal ? findCppUsingFacts(literal) : primaryUsings;
|
|
7671
|
+
const key = fact => `${fact.line}:${fact.kind}:${fact.target}`;
|
|
7672
|
+
const original = new Map(originalUsings.map(fact => [key(fact), fact]));
|
|
7673
|
+
const usings = mergeExtracted(primaryUsings, originalUsings, key).map(fact => {
|
|
7674
|
+
const groups = original.get(key(fact))?.ppGroups;
|
|
7675
|
+
return groups ? { ...fact, ppGroups: groups } : fact;
|
|
7676
|
+
});
|
|
6734
7677
|
if (usings.length > 0) result.cppUsings = usings;
|
|
6735
7678
|
}
|
|
6736
7679
|
// Adapter-only full-analysis fact: keep the public parse result shape
|
|
@@ -6743,6 +7686,7 @@ function parse(code, parser, mode, options = {}) {
|
|
|
6743
7686
|
});
|
|
6744
7687
|
return result;
|
|
6745
7688
|
} finally {
|
|
7689
|
+
if (rawCapture?.tree && rawCapture.tree !== tree && rawCapture.tree !== literal) rawCapture.tree.delete?.();
|
|
6746
7690
|
if (options.releaseAnalysisTree) {
|
|
6747
7691
|
releaseCFamilyTree(parser, code, tree);
|
|
6748
7692
|
}
|
|
@@ -6766,7 +7710,7 @@ function languageBranchExtraction(code, mode) {
|
|
|
6766
7710
|
return {
|
|
6767
7711
|
functions: findFunctionsInTree(branch.source, tree, branch.language, lines).map(tag),
|
|
6768
7712
|
classes,
|
|
6769
|
-
calls: findCallsInTree(branch.source, branchParser, {}, tree, true).map(tag),
|
|
7713
|
+
calls: findCallsInTree(branch.source, branchParser, {}, tree, true, branch.language).map(tag),
|
|
6770
7714
|
};
|
|
6771
7715
|
} finally {
|
|
6772
7716
|
if (isMainThread) tree.delete?.();
|
|
@@ -6793,6 +7737,8 @@ function createCFamilyLanguage(mode) {
|
|
|
6793
7737
|
findMacros,
|
|
6794
7738
|
findCallsInCode: (code, parser, options, existingTree, includeMacroBodies) =>
|
|
6795
7739
|
findCallsInCode(code, parser, options, existingTree, includeMacroBodies, mode),
|
|
7740
|
+
implicitBaseCallsOf: (code, parser, ownerLine, ownerEndLine) =>
|
|
7741
|
+
implicitBaseCallsOf(code, parser, ownerLine, ownerEndLine, mode),
|
|
6796
7742
|
findImportsInCode,
|
|
6797
7743
|
findExportsInCode: (code, parser) => findExportsInCodeShallow(code, parser, mode),
|
|
6798
7744
|
findUsagesInCode: (code, name, parser, existingTree, options) =>
|
|
@@ -6859,11 +7805,19 @@ function objectMacroReading(parser, bodies) {
|
|
|
6859
7805
|
}
|
|
6860
7806
|
|
|
6861
7807
|
module.exports = {
|
|
7808
|
+
deductionGuideOf,
|
|
7809
|
+
endsFunctionHead,
|
|
7810
|
+
withoutDecorationMacros,
|
|
7811
|
+
implicitOwnerScope,
|
|
7812
|
+
implicitConstructorInfo,
|
|
7813
|
+
isInvokedName,
|
|
6862
7814
|
createCFamilyLanguage,
|
|
6863
7815
|
objectMacroReading,
|
|
6864
7816
|
withExternalMacros,
|
|
7817
|
+
hasExternalMacroContext: () => externalMacroContext !== null,
|
|
6865
7818
|
// Deterministic test seams for the recovery memory/order contracts.
|
|
6866
7819
|
conditionalRecoverySources,
|
|
7820
|
+
conditionalHeaderText,
|
|
6867
7821
|
mergeExtracted,
|
|
6868
7822
|
macroAccessEffect,
|
|
6869
7823
|
};
|