homegraph 1.6.1 → 1.6.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.
Files changed (40) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/bin/homegraph.js +2 -2
  3. package/dist/db/index.js +1 -0
  4. package/dist/db/migrations.d.ts +6 -1
  5. package/dist/db/migrations.js +26 -1
  6. package/dist/db/queries.d.ts +5 -0
  7. package/dist/db/queries.js +17 -7
  8. package/dist/db/schema.sql +4 -0
  9. package/dist/extraction/grammars.js +2 -1
  10. package/dist/extraction/languages/c-cpp.d.ts +8 -0
  11. package/dist/extraction/languages/c-cpp.js +44 -0
  12. package/dist/extraction/wasm-runtime-flags.d.ts +4 -0
  13. package/dist/extraction/wasm-runtime-flags.js +9 -0
  14. package/dist/index.js +7 -1
  15. package/dist/installer/targets/icode.d.ts +43 -0
  16. package/dist/installer/targets/icode.js +631 -0
  17. package/dist/installer/targets/registry.js +2 -0
  18. package/dist/installer/targets/types.d.ts +1 -1
  19. package/dist/mcp/evidence-rendering.js +1 -1
  20. package/dist/mcp/explore-repeat-guard.d.ts +19 -5
  21. package/dist/mcp/explore-repeat-guard.js +35 -14
  22. package/dist/mcp/explore-session-state.d.ts +5 -0
  23. package/dist/mcp/explore-session-state.js +1 -0
  24. package/dist/mcp/locate-contract.js +1 -1
  25. package/dist/mcp/server-instructions.d.ts +2 -2
  26. package/dist/mcp/server-instructions.js +10 -10
  27. package/dist/mcp/session.js +3 -2
  28. package/dist/mcp/tools.d.ts +21 -2
  29. package/dist/mcp/tools.js +109 -50
  30. package/dist/resolution/c-feature-variants.d.ts +40 -0
  31. package/dist/resolution/c-feature-variants.js +272 -0
  32. package/dist/resolution/cpp-macro-visibility.d.ts +21 -0
  33. package/dist/resolution/cpp-macro-visibility.js +393 -0
  34. package/dist/resolution/index.js +11 -0
  35. package/dist/resolution/name-matcher.js +49 -2
  36. package/dist/resolution/types.d.ts +8 -0
  37. package/dist/resolution/types.js +8 -0
  38. package/dist/sync/watcher.d.ts +30 -0
  39. package/dist/sync/watcher.js +45 -0
  40. package/package.json +1 -1
@@ -0,0 +1,272 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.clearCFeatureVariantMemos = clearCFeatureVariantMemos;
4
+ exports.isCFeatureVariant = isCFeatureVariant;
5
+ exports.isOffByDefault = isOffByDefault;
6
+ const cpp_macro_visibility_1 = require("./cpp-macro-visibility");
7
+ const MEMO = new WeakMap();
8
+ function clearCFeatureVariantMemos(context) {
9
+ MEMO.delete(context);
10
+ }
11
+ function memoFor(context) {
12
+ let memo = MEMO.get(context);
13
+ if (!memo) {
14
+ memo = { files: new Map(), buildText: new Map(), components: new Map(), cFiles: null, defines: new Map(), named: new Map(), answers: new Map() };
15
+ MEMO.set(context, memo);
16
+ }
17
+ return memo;
18
+ }
19
+ const and = (a, b) => (a === false || b === false ? false : a === true && b === true ? true : undefined);
20
+ const or = (a, b) => (a === true || b === true ? true : a === false && b === false ? false : undefined);
21
+ const not = (a) => (a === undefined ? undefined : !a);
22
+ const C_FAMILY_FILE = /\.(c|h|cc|cpp|cxx|c\+\+|hpp|hh|hxx|h\+\+|inl|ipp|tcc|inc|m|mm)$/i;
23
+ const PLATFORM_FLAGS = new Set(['WIN32', 'WIN64', 'linux', 'unix']);
24
+ const BUILD_FILES = [
25
+ 'Makefile', 'makefile', 'GNUmakefile', 'Makefile.am', 'Makefile.in', 'CMakeLists.txt', 'meson.build',
26
+ 'meson_options.txt', 'configure.ac', 'configure.in', 'config.h.in', 'BUILD', 'BUILD.bazel', 'SConstruct', 'SConscript',
27
+ ];
28
+ const dirOf = (file) => file.slice(0, Math.max(0, file.lastIndexOf('/')));
29
+ /** `a/b/c.h` → `a/b`, `a`, `` (the project root). */
30
+ function ancestors(file) {
31
+ const dirs = [];
32
+ for (let dir = dirOf(file);; dir = dirOf(dir)) {
33
+ dirs.push(dir);
34
+ if (dir === '')
35
+ return dirs;
36
+ }
37
+ }
38
+ /**
39
+ * Is `fn`'s definition compiled only by a build that sets a flag nothing in
40
+ * the project sets, for the call at `ref`? A branch around both the
41
+ * definition and the call, in the call's own file, decides nothing.
42
+ */
43
+ function isCFeatureVariant(fn, ref, context) {
44
+ const memo = memoFor(context);
45
+ const sameFile = fn.filePath === ref.filePath;
46
+ const key = `v\0${fn.id}\0${sameFile ? ref.line : dirOf(ref.filePath)}`;
47
+ const hit = memo.answers.get(key);
48
+ if (hit !== undefined)
49
+ return hit;
50
+ const file = directivesOf(fn.filePath, context, memo);
51
+ let groups = enclosingTests(file.conditionals, fn.startLine);
52
+ if (sameFile) {
53
+ const around = enclosingTests(file.conditionals, ref.line);
54
+ let shared = 0;
55
+ while (shared < groups.length && shared < around.length &&
56
+ groups[shared][0] === around[shared][0] && groups[shared].length === around[shared].length)
57
+ shared++;
58
+ groups = groups.slice(shared);
59
+ }
60
+ const off = groups.length > 0 && isOff(groups, file, [fn.filePath, ref.filePath], context, memo);
61
+ memo.answers.set(key, off);
62
+ return off;
63
+ }
64
+ /** Is line `line` of `path` compiled only by a build that sets a flag nothing in the project sets? */
65
+ function isOffByDefault(path, line, context) {
66
+ const memo = memoFor(context);
67
+ const key = `o\0${path}\0${line}`;
68
+ const hit = memo.answers.get(key);
69
+ if (hit !== undefined)
70
+ return hit;
71
+ const file = directivesOf(path, context, memo);
72
+ const groups = enclosingTests(file.conditionals, line);
73
+ const off = groups.length > 0 && isOff(groups, file, [path], context, memo);
74
+ memo.answers.set(key, off);
75
+ return off;
76
+ }
77
+ /** Is a branch of `groups` off when no flag `scope`'s files see is set? */
78
+ function isOff(groups, file, scope, context, memo) {
79
+ const dirs = [...new Set(scope.flatMap(ancestors))];
80
+ const flag = (name) => {
81
+ if (name.startsWith('_') || PLATFORM_FLAGS.has(name) || file.defines.has(name))
82
+ return undefined;
83
+ let named = memo.named.get(name);
84
+ // A word of its own, or a compiler flag's `-DX` / `/DX`.
85
+ if (!named)
86
+ memo.named.set(name, (named = new RegExp(`(?:^|[^\\w]|[-/]D)${name}\\b`, 'm')));
87
+ if (dirs.some((dir) => named.test(buildTextOf(dir, context, memo))))
88
+ return undefined;
89
+ const components = new Set(scope.map((f) => componentOf(dirOf(f), context, memo)));
90
+ return [...components].some((component) => isDefinedIn(name, component, context, memo)) ? undefined : false;
91
+ };
92
+ return groups.some((tests) => branchTruth(tests, flag) === false);
93
+ }
94
+ /**
95
+ * The branches around `line`, innermost last: for each, the tests of its
96
+ * group's branches up to and including the one `line` is in (`#else` has no
97
+ * test).
98
+ */
99
+ function enclosingTests(conditionals, line) {
100
+ const groups = [];
101
+ for (const d of conditionals) {
102
+ if (d.line >= line)
103
+ break;
104
+ if (d.op === 'if' || d.op === 'ifdef' || d.op === 'ifndef')
105
+ groups.push([d]);
106
+ else if (d.op === 'endif')
107
+ groups.pop();
108
+ else
109
+ groups[groups.length - 1]?.push(d);
110
+ }
111
+ return groups;
112
+ }
113
+ /** Whether the last branch of `tests` is taken: no earlier test held, and its own does. */
114
+ function branchTruth(tests, flag) {
115
+ let earlier = false;
116
+ for (let i = 0; i < tests.length - 1; i++) {
117
+ earlier = or(earlier, testTruth(tests[i], flag));
118
+ if (earlier === true)
119
+ return false;
120
+ }
121
+ return and(not(earlier), testTruth(tests[tests.length - 1], flag));
122
+ }
123
+ function testTruth(d, flag) {
124
+ const name = d.expression.trim().match(/^\w+$/)?.[0];
125
+ switch (d.op) {
126
+ case 'ifdef':
127
+ case 'elifdef':
128
+ return name ? flag(name) : undefined;
129
+ case 'ifndef':
130
+ case 'elifndef':
131
+ return name ? not(flag(name)) : undefined;
132
+ case 'else':
133
+ return true;
134
+ default:
135
+ return evaluate(d.expression, flag);
136
+ }
137
+ }
138
+ /**
139
+ * A `#if` expression of `defined`, flags, integers, `!`, `&&`, `||` and
140
+ * parentheses; anything else is unknown. A flag that is set nowhere reads as
141
+ * `0`, as the preprocessor reads a name no `#define` gave a value.
142
+ */
143
+ function evaluate(expression, flag) {
144
+ const tokens = expression.match(/0x[\da-f]+\w*|\d\w*|[A-Za-z_]\w*|&&|\|\||\S/gi) ?? [];
145
+ // Check the shape first, so an unsupported test reads no flag.
146
+ let i = 0;
147
+ const shape = () => {
148
+ const term = () => {
149
+ const t = tokens[i++];
150
+ if (t === '!')
151
+ return term();
152
+ if (t === '(')
153
+ return shape() && tokens[i++] === ')';
154
+ if (t === 'defined') {
155
+ const paren = tokens[i] === '(';
156
+ if (paren)
157
+ i++;
158
+ return /^[A-Za-z_]\w*$/.test(tokens[i++] ?? '') && (!paren || tokens[i++] === ')');
159
+ }
160
+ return t !== undefined && (/^(0x[\da-f]+|\d+)[ul]*$/i.test(t) || /^[A-Za-z_]\w*$/.test(t));
161
+ };
162
+ if (!term())
163
+ return false;
164
+ while (tokens[i] === '&&' || tokens[i] === '||') {
165
+ i++;
166
+ if (!term())
167
+ return false;
168
+ }
169
+ return true;
170
+ };
171
+ if (tokens.length === 0 || !shape() || i !== tokens.length)
172
+ return undefined;
173
+ i = 0;
174
+ const disjunction = () => {
175
+ let value = conjunction();
176
+ while (tokens[i] === '||') {
177
+ i++;
178
+ value = or(value, conjunction());
179
+ }
180
+ return value;
181
+ };
182
+ const conjunction = () => {
183
+ let value = term();
184
+ while (tokens[i] === '&&') {
185
+ i++;
186
+ value = and(value, term());
187
+ }
188
+ return value;
189
+ };
190
+ const term = () => {
191
+ const t = tokens[i++];
192
+ if (t === '!')
193
+ return not(term());
194
+ if (t === '(') {
195
+ const value = disjunction();
196
+ i++;
197
+ return value;
198
+ }
199
+ if (t === 'defined') {
200
+ if (tokens[i] === '(') {
201
+ const value = flag(tokens[i + 1]);
202
+ i += 3;
203
+ return value;
204
+ }
205
+ return flag(tokens[i++]);
206
+ }
207
+ if (/^\d|^0x/i.test(t))
208
+ return Number.parseInt(t.replace(/[ul]+$/i, ''), /^0x/i.test(t) ? 16 : 10) !== 0;
209
+ return flag(t) === false ? false : undefined;
210
+ };
211
+ return disjunction();
212
+ }
213
+ function directivesOf(file, context, memo) {
214
+ const hit = memo.files.get(file);
215
+ if (hit)
216
+ return hit;
217
+ const lines = (0, cpp_macro_visibility_1.directiveLines)(context.readFile(file) ?? '');
218
+ const conditionals = [];
219
+ const defines = new Set();
220
+ for (let i = 0; i < lines.length; i++) {
221
+ const define = lines[i].match(/^\s*#\s*define\s+(\w+)/);
222
+ if (define) {
223
+ defines.add(define[1]);
224
+ continue;
225
+ }
226
+ const m = lines[i].match(/^\s*#\s*(ifdef|ifndef|if|elifdef|elifndef|elif|else|endif)\b(.*)$/);
227
+ if (!m)
228
+ continue;
229
+ // A test continued with a backslash goes on over the next lines.
230
+ let expression = m[2];
231
+ for (let j = i; /\\\s*$/.test(lines[j]) && j + 1 < lines.length; j++) {
232
+ expression = expression.replace(/\\\s*$/, ' ') + lines[j + 1];
233
+ }
234
+ conditionals.push({ op: m[1], expression, line: i + 1 });
235
+ }
236
+ const directives = { conditionals, defines };
237
+ memo.files.set(file, directives);
238
+ return directives;
239
+ }
240
+ function buildTextOf(dir, context, memo) {
241
+ const hit = memo.buildText.get(dir);
242
+ if (hit !== undefined)
243
+ return hit;
244
+ const text = BUILD_FILES.map((name) => context.readFile(dir ? `${dir}/${name}` : name) ?? '').join('\n');
245
+ memo.buildText.set(dir, text);
246
+ return text;
247
+ }
248
+ /** The nearest directory at or above `dir` with a build file, else the project root. */
249
+ function componentOf(dir, context, memo) {
250
+ const hit = memo.components.get(dir);
251
+ if (hit !== undefined)
252
+ return hit;
253
+ const component = dir === '' || buildTextOf(dir, context, memo).trim() ? dir : componentOf(dirOf(dir), context, memo);
254
+ memo.components.set(dir, component);
255
+ return component;
256
+ }
257
+ /** Does a C or C++ file under `component` `#define` `name`? Read only from files whose bytes hold the name. */
258
+ function isDefinedIn(name, component, context, memo) {
259
+ const key = `${name}\0${component}`;
260
+ const hit = memo.defines.get(key);
261
+ if (hit !== undefined)
262
+ return hit;
263
+ memo.cFiles ??= context.getAllFiles().filter((file) => C_FAMILY_FILE.test(file));
264
+ const prefix = component ? `${component}/` : '';
265
+ const define = new RegExp(`^[ \\t]*#[ \\t]*define[ \\t]+${name}\\b`, 'm');
266
+ const defined = memo.cFiles.some((file) => file.startsWith(prefix) &&
267
+ (context.fileContains ? context.fileContains(file, name) : true) &&
268
+ define.test(context.readFile(file) ?? ''));
269
+ memo.defines.set(key, defined);
270
+ return defined;
271
+ }
272
+ //# sourceMappingURL=c-feature-variants.js.map
@@ -0,0 +1,21 @@
1
+ import { type ResolutionContext, type UnresolvedRef } from './types';
2
+ export declare function clearCppMacroVisibility(context: ResolutionContext): void;
3
+ /**
4
+ * Is this C/C++ `calls` ref a macro expansion rather than a call? True when
5
+ * the index knows the name as a function-like macro (extraction mints a
6
+ * `constant` per `preproc_function_def`) and either nothing but macros bears
7
+ * the name — there is no function to call, and a case-insensitive fuzzy
8
+ * match (`SWAP` → `swap`) must not invent one — or the macro is definitely
9
+ * visible at the call site.
10
+ */
11
+ export declare function isVisibleCppMacro(ref: UnresolvedRef, context: ResolutionContext): boolean;
12
+ /**
13
+ * The file's lines with comments removed — only as far as the preprocessor
14
+ * needs: a line inside a block comment is blank, a directive line loses its
15
+ * trailing `//` / `/* … *\/`, and every other line is kept verbatim (its
16
+ * content is never read, only whether a block comment opens on it). The
17
+ * generic comment stripper is a regex pass over the whole file and this walk
18
+ * touches every header of a translation unit, vendor trees included.
19
+ */
20
+ export declare function directiveLines(source: string): string[];
21
+ //# sourceMappingURL=cpp-macro-visibility.d.ts.map
@@ -0,0 +1,393 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.clearCppMacroVisibility = clearCppMacroVisibility;
37
+ exports.isVisibleCppMacro = isVisibleCppMacro;
38
+ exports.directiveLines = directiveLines;
39
+ /**
40
+ * Is a C/C++ call's name a function-like macro visible at the call site?
41
+ *
42
+ * `TRACE_POINT(1)` parses as a call_expression, so extraction records a
43
+ * `calls` ref named `TRACE_POINT`. If the translation unit defines
44
+ * `#define TRACE_POINT(value) ...` (in the file itself or through an
45
+ * include), the "call" is a macro expansion — and binding it to a
46
+ * same-spelled free function in an unrelated file fabricates a caller and a
47
+ * callee that never existed (#1838). The resolver asks this before trying
48
+ * any strategy and drops such a ref.
49
+ *
50
+ * Directive summaries are cached per file, but evaluated in translation-unit
51
+ * order on every inclusion. Only #pragma once and actual guard state suppress
52
+ * reinclusion; an active recursion stack breaks include cycles. Unknown build
53
+ * flags remain possible, so only definite macro visibility suppresses a call.
54
+ * Object-like definitions participate in conditions but never enter the cached
55
+ * call-site timelines (vendor headers can contain tens of thousands of them).
56
+ *
57
+ * A guard the walk cannot decide (`#define X_H 1`, or any header reached under
58
+ * an unknown `#if`) re-enters its header on every inclusion path, which is
59
+ * exponential in the include graph's depth (#2127). A re-entry that provably
60
+ * repeats an earlier visit which changed nothing is skipped, and a hard budget
61
+ * bounds whatever is left: past it, nothing is known and nothing is suppressed.
62
+ */
63
+ const path = __importStar(require("path"));
64
+ const c_cpp_1 = require("../extraction/languages/c-cpp");
65
+ const types_1 = require("./types");
66
+ const import_resolver_1 = require("./import-resolver");
67
+ const memo = new WeakMap();
68
+ const ROOT_TIMELINE_CAP = 32;
69
+ /** Directive events one translation-unit walk may evaluate (#2127). */
70
+ const WALK_EVENT_BUDGET = 1_000_000;
71
+ const NO_CUTS = [];
72
+ const and = (a, b) => a === false || b === false ? false : a === true && b === true ? true : undefined;
73
+ const or = (a, b) => a === true || b === true ? true : a === false && b === false ? false : undefined;
74
+ const not = (a) => (a === undefined ? undefined : !a);
75
+ function clearCppMacroVisibility(context) {
76
+ memo.delete(context);
77
+ }
78
+ const isDefine = (n) => n.kind === 'constant' && types_1.CPP_DEFINE_SIGNATURE.test(n.signature ?? '');
79
+ /**
80
+ * Is this C/C++ `calls` ref a macro expansion rather than a call? True when
81
+ * the index knows the name as a function-like macro (extraction mints a
82
+ * `constant` per `preproc_function_def`) and either nothing but macros bears
83
+ * the name — there is no function to call, and a case-insensitive fuzzy
84
+ * match (`SWAP` → `swap`) must not invent one — or the macro is definitely
85
+ * visible at the call site.
86
+ */
87
+ function isVisibleCppMacro(ref, context) {
88
+ if (ref.language !== 'c' && ref.language !== 'cpp')
89
+ return false;
90
+ if (ref.referenceKind !== 'calls' || !/^\w+$/.test(ref.referenceName))
91
+ return false;
92
+ // The name cache is the index's truth, invalidated with the resolver's other
93
+ // caches; a per-context name set would go stale between indexing batches.
94
+ const sameName = context.getNodesByName(ref.referenceName);
95
+ if (!sameName.some(isDefine))
96
+ return false;
97
+ if (!sameName.some((n) => !isDefine(n)))
98
+ return true;
99
+ const cache = cacheFor(context);
100
+ const rootKey = `${ref.language}\0${ref.filePath}`;
101
+ let timeline = cache.roots.get(rootKey);
102
+ if (!timeline) {
103
+ timeline = walkTranslationUnit(ref.filePath, ref.language, context, cache);
104
+ if (cache.roots.size >= ROOT_TIMELINE_CAP)
105
+ cache.roots.delete(cache.roots.keys().next().value);
106
+ cache.roots.set(rootKey, timeline);
107
+ }
108
+ if (ref.line >= timeline.cutoff)
109
+ return false;
110
+ const before = (timeline.events.get(ref.referenceName) ?? []).filter((e) => e.line <= ref.line);
111
+ return before.length > 0 && before[before.length - 1].defined === true;
112
+ }
113
+ function cacheFor(context) {
114
+ let cache = memo.get(context);
115
+ if (!cache) {
116
+ cache = { summaries: new Map(), includes: new Map(), byBasename: null, roots: new Map() };
117
+ memo.set(context, cache);
118
+ }
119
+ return cache;
120
+ }
121
+ /** Cache syntax, never conditional truth: an included file can change its flags. */
122
+ function summarize(file, context, cache) {
123
+ const cached = cache.summaries.get(file);
124
+ if (cached)
125
+ return cached;
126
+ const events = [];
127
+ const lines = directiveLines(context.readFile(file) ?? '');
128
+ for (let i = 0; i < lines.length; i++) {
129
+ const text = lines[i];
130
+ const branch = text.match(/^\s*#\s*(ifdef|ifndef|if|elif|else|endif)\b(.*)$/);
131
+ if (branch) {
132
+ events.push({ kind: 'branch', op: branch[1], expression: branch[2],
133
+ guard: guardsItself(lines, i, branch[1], branch[2]), line: i + 1 });
134
+ continue;
135
+ }
136
+ const directive = text.match(/^\s*#\s*(define|undef)\s+(\w+)(\(?)/);
137
+ if (directive) {
138
+ events.push({ kind: directive[1], name: directive[2], line: i + 1,
139
+ functionLike: directive[3] === '(', value: text.slice(directive[0].length),
140
+ wrapsItself: directive[3] === '(' && callsItself(lines, i, directive[2]) });
141
+ continue;
142
+ }
143
+ const include = text.match(/^\s*#\s*include\s*([<"])([^>"]+)[>"]/);
144
+ if (include)
145
+ events.push({ kind: 'include', quote: include[1], spec: include[2], line: i + 1 });
146
+ if (/^\s*#\s*pragma\s+once\b/.test(text))
147
+ events.push({ kind: 'once', line: i + 1 });
148
+ }
149
+ cache.summaries.set(file, events);
150
+ return events;
151
+ }
152
+ /**
153
+ * The file's lines with comments removed — only as far as the preprocessor
154
+ * needs: a line inside a block comment is blank, a directive line loses its
155
+ * trailing `//` / `/* … *\/`, and every other line is kept verbatim (its
156
+ * content is never read, only whether a block comment opens on it). The
157
+ * generic comment stripper is a regex pass over the whole file and this walk
158
+ * touches every header of a translation unit, vendor trees included.
159
+ */
160
+ function directiveLines(source) {
161
+ const out = [];
162
+ let inBlock = false;
163
+ for (const raw of (0, c_cpp_1.maskCppRawStrings)(source).source.split(/\r?\n/)) {
164
+ let text = raw;
165
+ if (inBlock) {
166
+ const end = text.indexOf('*/');
167
+ if (end < 0) {
168
+ out.push('');
169
+ continue;
170
+ }
171
+ text = text.slice(end + 2);
172
+ inBlock = false;
173
+ }
174
+ const directive = /^\s*#/.test(text);
175
+ let kept = '';
176
+ let quote = '';
177
+ for (let i = 0; i < text.length; i++) {
178
+ const c = text[i];
179
+ if (quote) {
180
+ if (c === '\\')
181
+ i++;
182
+ else if (c === quote)
183
+ quote = '';
184
+ continue;
185
+ }
186
+ if (c === '"' || c === "'") {
187
+ quote = c;
188
+ continue;
189
+ }
190
+ if (c === '/' && text[i + 1] === '/') {
191
+ kept = text.slice(0, i);
192
+ break;
193
+ }
194
+ if (c === '/' && text[i + 1] === '*') {
195
+ const end = text.indexOf('*/', i + 2);
196
+ if (end < 0) {
197
+ inBlock = true;
198
+ kept = text.slice(0, i);
199
+ break;
200
+ }
201
+ text = text.slice(0, i) + ' ' + text.slice(end + 2);
202
+ i--;
203
+ continue;
204
+ }
205
+ }
206
+ out.push(directive ? kept || text : raw);
207
+ }
208
+ return out;
209
+ }
210
+ /** Does the body of the `#define NAME(` at `index` (continuation lines included) call `NAME`? */
211
+ function callsItself(lines, index, name) {
212
+ let text = lines[index];
213
+ for (let j = index; /\\\s*$/.test(lines[j]) && j + 1 < lines.length; j++)
214
+ text += ' ' + lines[j + 1];
215
+ const body = text.slice(text.indexOf('(') + 1);
216
+ return new RegExp(`(?:\\b${name}\\s*\\(|\\(\\s*${name}\\s*\\)\\s*\\()`).test(body);
217
+ }
218
+ /**
219
+ * The include-guard idiom: `#ifndef X_H` (or `#if !defined(X_H)`) whose next
220
+ * directive is `#define X_H`. Nothing in the file defines the guard before
221
+ * the test, so this is the first inclusion and the guarded body is active.
222
+ * The same shape around a fallback function-like macro (`#ifndef MIN` /
223
+ * `#define MIN(a, b) …`) is read the same way. A default VALUE
224
+ * (`#ifndef ENABLE_X` / `#define ENABLE_X 0`) is not: that is the flag a
225
+ * build overrides on the command line, so it stays unknown.
226
+ */
227
+ function guardsItself(lines, index, op, expression) {
228
+ const name = op === 'ifndef'
229
+ ? expression.trim()
230
+ : expression.match(/^\s*!\s*defined\s*(?:\(\s*(\w+)\s*\)|(\w+))\s*$/)?.slice(1).find(Boolean);
231
+ if (!name || !/^\w+$/.test(name))
232
+ return false;
233
+ for (let j = index + 1; j < lines.length; j++) {
234
+ const text = lines[j];
235
+ if (!/^\s*#/.test(text))
236
+ continue;
237
+ return new RegExp(`^\\s*#\\s*define\\s+${name}(?:\\s*$|\\()`).test(text);
238
+ }
239
+ return false;
240
+ }
241
+ function resolveInclude(file, quote, spec, language, context, cache) {
242
+ const key = `${language}\0${file}\0${quote}${spec}`;
243
+ const cached = cache.includes.get(key);
244
+ if (cached !== undefined)
245
+ return cached;
246
+ const local = path.posix.normalize(path.posix.join(path.posix.dirname(file), spec.replace(/\\/g, '/')));
247
+ let target = quote === '"' && !local.startsWith('../') && !path.posix.isAbsolute(local) && context.fileExists(local)
248
+ ? local
249
+ : (0, import_resolver_1.resolveImportPath)(spec, file, language, context);
250
+ if (!target) {
251
+ if (!cache.byBasename) {
252
+ cache.byBasename = new Map();
253
+ for (const f of context.getAllFiles()) {
254
+ const base = f.slice(f.lastIndexOf('/') + 1);
255
+ const list = cache.byBasename.get(base);
256
+ if (list)
257
+ list.push(f);
258
+ else
259
+ cache.byBasename.set(base, [f]);
260
+ }
261
+ }
262
+ const normalized = spec.replace(/\\/g, '/');
263
+ const matches = (cache.byBasename.get(normalized.slice(normalized.lastIndexOf('/') + 1)) ?? []).filter((f) => f === normalized || f.endsWith('/' + normalized));
264
+ if (matches.length === 1)
265
+ target = matches[0];
266
+ }
267
+ cache.includes.set(key, target);
268
+ return target;
269
+ }
270
+ function walkTranslationUnit(rootFile, language, context, cache) {
271
+ const timeline = new Map();
272
+ const definitions = new Map();
273
+ const scanning = new Set();
274
+ const macroNames = new Set();
275
+ const once = new Map();
276
+ // Every change a later directive could observe (definitions, `#pragma once`,
277
+ // the macro-name set) bumps `version`. A visit that left it unchanged, entered
278
+ // again with the same inherited truth while it is still unchanged, starts from
279
+ // the same state, so it would take the same branches and change nothing again;
280
+ // the events it would push repeat each name's current state, which is already
281
+ // its last event. Include cycles cut by the recursion stack (`cuts`) must still
282
+ // be cut for the repeat to hold.
283
+ let version = 0;
284
+ const cuts = [];
285
+ const visits = new Map();
286
+ let budget = WALK_EVENT_BUDGET;
287
+ let cutoff = Infinity;
288
+ const condition = (expression) => {
289
+ const text = expression.trim();
290
+ if (/^(?:0x[\da-f]+|\d+)[uUlL]*$/i.test(text))
291
+ return Number(text.replace(/[uUlL]+$/, '')) !== 0;
292
+ const def = text.match(/^(!)?\s*defined\s*(?:\(\s*(\w+)\s*\)|(\w+))$/);
293
+ if (def) {
294
+ const known = definitions.get(def[2] ?? def[3])?.defined;
295
+ return def[1] ? not(known) : known;
296
+ }
297
+ return /^\w+$/.test(text) ? definitions.get(text)?.value : undefined;
298
+ };
299
+ const scan = (file, inherited, includeLine) => {
300
+ if (inherited === false || once.get(file) === true || cutoff !== Infinity)
301
+ return;
302
+ if (scanning.has(file)) {
303
+ cuts.push(file);
304
+ return;
305
+ }
306
+ const seen = visits.get(file);
307
+ if (seen && seen.inherited === inherited && seen.start === seen.end && seen.end === version &&
308
+ seen.cuts.every((c) => scanning.has(c))) {
309
+ cuts.push(...seen.cuts);
310
+ return;
311
+ }
312
+ const start = version;
313
+ const firstCut = cuts.length;
314
+ scanning.add(file);
315
+ let active = inherited;
316
+ const frames = [];
317
+ for (const ev of summarize(file, context, cache)) {
318
+ const line = includeLine ?? ev.line;
319
+ if (cutoff === Infinity && --budget < 0)
320
+ cutoff = line;
321
+ if (cutoff !== Infinity)
322
+ break;
323
+ if (ev.kind === 'branch') {
324
+ if (ev.op === 'if' || ev.op === 'ifdef' || ev.op === 'ifndef') {
325
+ const known = definitions.get(ev.expression.trim())?.defined;
326
+ let selected = ev.op === 'if' ? condition(ev.expression) : ev.op === 'ifndef' ? not(known) : known;
327
+ if (selected === undefined && ev.guard)
328
+ selected = true;
329
+ frames.push({ parent: active, taken: selected });
330
+ active = and(active, selected);
331
+ }
332
+ else if (ev.op === 'endif') {
333
+ active = frames.pop()?.parent ?? inherited;
334
+ }
335
+ else {
336
+ const frame = frames[frames.length - 1];
337
+ if (frame) {
338
+ const test = ev.op === 'else' ? true : condition(ev.expression);
339
+ active = and(frame.parent, and(not(frame.taken), test));
340
+ frame.taken = or(frame.taken, test);
341
+ }
342
+ }
343
+ continue;
344
+ }
345
+ if (active === false)
346
+ continue;
347
+ if (ev.kind === 'once') {
348
+ const next = or(once.get(file) ?? false, active);
349
+ if (next !== (once.get(file) ?? false))
350
+ version++;
351
+ once.set(file, next);
352
+ continue;
353
+ }
354
+ if (ev.kind === 'include') {
355
+ const target = resolveInclude(file, ev.quote, ev.spec, language, context, cache);
356
+ if (target)
357
+ scan(target, active, line);
358
+ continue;
359
+ }
360
+ const prior = definitions.get(ev.name);
361
+ const defining = ev.kind === 'define';
362
+ const macro = defining && ev.functionLike && !ev.wrapsItself;
363
+ const now = active === true ? macro : prior?.macro === macro ? macro : undefined;
364
+ // A name no directive has touched is unknown, not undefined: the build
365
+ // can set it on the command line. So an `#undef` under an undecidable
366
+ // `#if` leaves it unknown (#2069); only a certain one clears it.
367
+ const next = {
368
+ defined: defining ? or(prior?.defined, active) : and(prior?.defined, not(active)),
369
+ value: defining && active === true ? condition(ev.value) : undefined,
370
+ macro: now,
371
+ };
372
+ if (next.defined !== prior?.defined || next.value !== prior?.value || next.macro !== prior?.macro)
373
+ version++;
374
+ definitions.set(ev.name, next);
375
+ if (ev.functionLike && !macroNames.has(ev.name)) {
376
+ macroNames.add(ev.name);
377
+ version++;
378
+ }
379
+ if (macroNames.has(ev.name)) {
380
+ const events = timeline.get(ev.name) ?? [];
381
+ events.push({ line, defined: now });
382
+ timeline.set(ev.name, events);
383
+ }
384
+ }
385
+ scanning.delete(file);
386
+ const own = cuts.length > firstCut ? [...new Set(cuts.splice(firstCut))] : NO_CUTS;
387
+ cuts.push(...own);
388
+ visits.set(file, { inherited, start, end: version, cuts: own });
389
+ };
390
+ scan(rootFile, true);
391
+ return { events: timeline, cutoff };
392
+ }
393
+ //# sourceMappingURL=cpp-macro-visibility.js.map