@photostructure/fs-metadata 0.6.1 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +7 -1
- package/CLAUDE.md +141 -315
- package/CODE_OF_CONDUCT.md +11 -11
- package/CONTRIBUTING.md +1 -1
- package/README.md +34 -103
- package/binding.gyp +97 -22
- package/claude.sh +23 -0
- package/dist/index.cjs +51 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.mjs +51 -21
- package/dist/index.mjs.map +1 -1
- package/doc/C++_REVIEW_TODO.md +97 -25
- package/doc/GPG_RELEASE_HOWTO.md +44 -13
- package/doc/MACOS_API_REFERENCE.md +469 -0
- package/doc/SECURITY_AUDIT_2025.md +809 -0
- package/doc/SSH_RELEASE_HOWTO.md +28 -24
- package/doc/WINDOWS_API_REFERENCE.md +422 -0
- package/doc/WINDOWS_ARM64_SECURITY.md +161 -0
- package/doc/WINDOWS_DEBUG_GUIDE.md +9 -2
- package/doc/examples.md +267 -0
- package/doc/gotchas.md +297 -0
- package/doc/logo.png +0 -0
- package/doc/logo.svg +85 -0
- package/doc/macos-asan-sip-issue.md +71 -0
- package/doc/social.png +0 -0
- package/doc/social.svg +125 -0
- package/doc/windows-build.md +226 -0
- package/doc/windows-clang-tidy.md +72 -0
- package/doc/windows-memory-testing.md +108 -0
- package/doc/windows-prebuildify-arm64.md +232 -0
- package/jest.config.cjs +23 -0
- package/package.json +61 -36
- package/prebuilds/darwin-arm64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/darwin-x64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/linux-arm64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/linux-arm64/@photostructure+fs-metadata.musl.node +0 -0
- package/prebuilds/linux-x64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/linux-x64/@photostructure+fs-metadata.musl.node +0 -0
- package/prebuilds/win32-arm64/@photostructure+fs-metadata.glibc.node +0 -0
- package/prebuilds/win32-x64/@photostructure+fs-metadata.glibc.node +0 -0
- package/scripts/check-memory.ts +186 -0
- package/scripts/clang-tidy.ts +690 -99
- package/scripts/install.cjs +42 -0
- package/scripts/is-platform.mjs +1 -1
- package/scripts/macos-asan.sh +155 -0
- package/scripts/post-build.mjs +3 -3
- package/scripts/prebuild-linux-glibc.sh +12 -1
- package/scripts/prebuildify-wrapper.ts +77 -0
- package/scripts/precommit.ts +45 -20
- package/scripts/sanitizers-test.sh +1 -1
- package/src/common/volume_metadata.h +6 -0
- package/src/darwin/hidden.cpp +73 -25
- package/src/darwin/path_security.h +149 -0
- package/src/darwin/raii_utils.h +104 -4
- package/src/darwin/volume_metadata.cpp +132 -58
- package/src/darwin/volume_mount_points.cpp +80 -47
- package/src/hidden.ts +36 -13
- package/src/linux/gio_mount_points.cpp +17 -18
- package/src/linux/gio_utils.cpp +92 -37
- package/src/linux/gio_utils.h +11 -5
- package/src/linux/gio_volume_metadata.cpp +111 -48
- package/src/linux/volume_metadata.cpp +67 -4
- package/src/object.ts +1 -0
- package/src/options.ts +6 -0
- package/src/path.ts +11 -0
- package/src/remote_info.ts +5 -3
- package/src/stack_path.ts +8 -6
- package/src/string_enum.ts +1 -0
- package/src/test-utils/memory-test-core.ts +336 -0
- package/src/test-utils/memory-test-runner.ts +108 -0
- package/src/test-utils/platform.ts +46 -1
- package/src/test-utils/worker-thread-helper.cjs +154 -27
- package/src/types/native_bindings.ts +1 -1
- package/src/types/options.ts +6 -0
- package/src/windows/drive_status.h +133 -163
- package/src/windows/error_utils.h +54 -3
- package/src/windows/fs_meta.h +1 -1
- package/src/windows/hidden.cpp +60 -43
- package/src/windows/security_utils.h +250 -0
- package/src/windows/string.h +68 -11
- package/src/windows/system_volume.h +1 -1
- package/src/windows/thread_pool.h +206 -0
- package/src/windows/volume_metadata.cpp +11 -6
- package/src/windows/volume_mount_points.cpp +8 -7
- package/src/windows/windows_arch.h +39 -0
- package/scripts/check-memory.mjs +0 -123
package/scripts/clang-tidy.ts
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env tsx
|
|
2
2
|
import { exec as execCallback, execSync } from "node:child_process";
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
3
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { cpus, platform } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
5
6
|
import { promisify } from "node:util";
|
|
6
7
|
|
|
7
8
|
const exec = promisify(execCallback);
|
|
8
9
|
|
|
9
|
-
//
|
|
10
|
-
if (
|
|
11
|
-
console.log("Skipping clang-tidy
|
|
10
|
+
// Check for environment variable to skip
|
|
11
|
+
if (process.env.SKIP_CLANG_TIDY) {
|
|
12
|
+
console.log("Skipping clang-tidy (SKIP_CLANG_TIDY is set)");
|
|
12
13
|
process.exit(0);
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
// Platform detection
|
|
17
|
+
const isWindows = platform() === "win32";
|
|
18
|
+
const isMacOS = platform() === "darwin";
|
|
19
|
+
const isLinux = platform() === "linux";
|
|
20
|
+
|
|
15
21
|
// Colors for output
|
|
16
22
|
const colors = {
|
|
17
23
|
reset: "\x1b[0m",
|
|
@@ -22,8 +28,20 @@ const colors = {
|
|
|
22
28
|
dim: "\x1b[2m",
|
|
23
29
|
} as const;
|
|
24
30
|
|
|
25
|
-
//
|
|
31
|
+
// Platform-specific warnings
|
|
32
|
+
if (isMacOS) {
|
|
33
|
+
console.log(
|
|
34
|
+
"Note: clang-tidy on macOS with Homebrew LLVM may report false positives",
|
|
35
|
+
);
|
|
36
|
+
console.log(
|
|
37
|
+
"due to header path issues. Set SKIP_CLANG_TIDY=1 to skip this check.",
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check for required tools (non-Windows only)
|
|
26
42
|
function checkCommand(command: string, installHint: string): boolean {
|
|
43
|
+
if (isWindows) return true; // Skip on Windows
|
|
44
|
+
|
|
27
45
|
try {
|
|
28
46
|
execSync(`which ${command}`, { stdio: "ignore" });
|
|
29
47
|
return true;
|
|
@@ -34,136 +52,606 @@ function checkCommand(command: string, installHint: string): boolean {
|
|
|
34
52
|
}
|
|
35
53
|
}
|
|
36
54
|
|
|
37
|
-
|
|
38
|
-
|
|
55
|
+
// Find clang-tidy binary
|
|
56
|
+
function findClangTidy(): string | null {
|
|
57
|
+
if (isWindows) {
|
|
58
|
+
// Windows-specific paths
|
|
59
|
+
const windowsPaths = [
|
|
60
|
+
"C:\\Program Files\\LLVM\\bin\\clang-tidy.exe",
|
|
61
|
+
"C:\\Program Files (x86)\\LLVM\\bin\\clang-tidy.exe",
|
|
62
|
+
// Visual Studio 2022
|
|
63
|
+
"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\Llvm\\x64\\bin\\clang-tidy.exe",
|
|
64
|
+
"C:\\Program Files\\Microsoft Visual Studio\\2022\\Professional\\VC\\Tools\\Llvm\\x64\\bin\\clang-tidy.exe",
|
|
65
|
+
"C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Tools\\Llvm\\x64\\bin\\clang-tidy.exe",
|
|
66
|
+
// Visual Studio 2019
|
|
67
|
+
"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\Llvm\\bin\\clang-tidy.exe",
|
|
68
|
+
"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Professional\\VC\\Tools\\Llvm\\bin\\clang-tidy.exe",
|
|
69
|
+
"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Enterprise\\VC\\Tools\\Llvm\\bin\\clang-tidy.exe",
|
|
70
|
+
];
|
|
39
71
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
72
|
+
for (const path of windowsPaths) {
|
|
73
|
+
if (existsSync(path)) {
|
|
74
|
+
return path;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Try to find in PATH
|
|
79
|
+
try {
|
|
80
|
+
execSync("where clang-tidy", { stdio: "ignore" });
|
|
81
|
+
return "clang-tidy";
|
|
82
|
+
} catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
} else {
|
|
86
|
+
// Unix-like systems
|
|
87
|
+
const versions = ["", "-18", "-17", "-16", "-15", "-14"];
|
|
88
|
+
for (const version of versions) {
|
|
89
|
+
try {
|
|
90
|
+
const result = execSync(`which clang-tidy${version}`, {
|
|
91
|
+
encoding: "utf8",
|
|
92
|
+
}).trim();
|
|
93
|
+
// On macOS, return the full path if it's in Homebrew
|
|
94
|
+
// This allows us to detect it for filtering purposes
|
|
95
|
+
if (
|
|
96
|
+
isMacOS &&
|
|
97
|
+
(result.includes("/opt/homebrew") ||
|
|
98
|
+
result.includes("/usr/local") ||
|
|
99
|
+
result.includes("/Cellar"))
|
|
100
|
+
) {
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
return `clang-tidy${version}`;
|
|
104
|
+
} catch {
|
|
105
|
+
// Continue trying
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// On macOS, check Homebrew locations explicitly
|
|
110
|
+
if (isMacOS) {
|
|
111
|
+
const brewPrefixes = ["/opt/homebrew", "/usr/local"];
|
|
112
|
+
for (const prefix of brewPrefixes) {
|
|
113
|
+
const paths = [
|
|
114
|
+
`${prefix}/opt/clang-tidy/bin/clang-tidy`,
|
|
115
|
+
`${prefix}/opt/llvm/bin/clang-tidy`,
|
|
116
|
+
];
|
|
54
117
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
) {
|
|
65
|
-
|
|
118
|
+
for (const path of paths) {
|
|
119
|
+
if (existsSync(path)) {
|
|
120
|
+
return path;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Try versioned LLVM formulas
|
|
125
|
+
for (let v = 18; v >= 14; v--) {
|
|
126
|
+
const versionedPath = `${prefix}/opt/llvm@${v}/bin/clang-tidy`;
|
|
127
|
+
if (existsSync(versionedPath)) {
|
|
128
|
+
return versionedPath;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return "clang-tidy"; // fallback
|
|
135
|
+
}
|
|
66
136
|
}
|
|
67
137
|
|
|
68
|
-
|
|
69
|
-
|
|
138
|
+
// Generate compile_commands.json for Windows
|
|
139
|
+
async function generateWindowsCompileCommands(): Promise<boolean> {
|
|
140
|
+
console.log("Generating compile_commands.json for Windows...");
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
execSync("npm run setup:native", { stdio: "inherit" });
|
|
144
|
+
|
|
145
|
+
const nodeVersion = process.version.slice(1);
|
|
146
|
+
|
|
147
|
+
// Try multiple possible locations for node-gyp headers
|
|
148
|
+
const possibleNodeGypPaths = [
|
|
149
|
+
`${process.env.USERPROFILE}\\.node-gyp\\${nodeVersion}`,
|
|
150
|
+
`${process.env.LOCALAPPDATA}\\node-gyp\\Cache\\${nodeVersion}`,
|
|
151
|
+
`${process.env.APPDATA}\\npm\\node_modules\\node-gyp\\cache\\${nodeVersion}`,
|
|
152
|
+
];
|
|
153
|
+
|
|
154
|
+
let nodeGyp = "";
|
|
155
|
+
for (const path of possibleNodeGypPaths) {
|
|
156
|
+
if (existsSync(join(path, "include", "node", "node.h"))) {
|
|
157
|
+
nodeGyp = path;
|
|
158
|
+
console.log("Found Node.js headers at:", nodeGyp);
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// If not found, install them
|
|
164
|
+
if (!nodeGyp) {
|
|
165
|
+
console.log("Installing Node.js headers...");
|
|
166
|
+
execSync("npx node-gyp install", { stdio: "inherit" });
|
|
167
|
+
|
|
168
|
+
// Check again
|
|
169
|
+
for (const path of possibleNodeGypPaths) {
|
|
170
|
+
if (existsSync(join(path, "include", "node", "node.h"))) {
|
|
171
|
+
nodeGyp = path;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (!nodeGyp) {
|
|
177
|
+
// Fallback to default
|
|
178
|
+
nodeGyp = `${process.env.USERPROFILE}\\.node-gyp\\${nodeVersion}`;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Get all Windows sources
|
|
183
|
+
const windowsSources: string[] = [];
|
|
184
|
+
if (existsSync(join("src", "windows"))) {
|
|
185
|
+
const entries = require("fs").readdirSync(join("src", "windows"));
|
|
186
|
+
for (const entry of entries) {
|
|
187
|
+
if (entry.endsWith(".cpp") || entry.endsWith(".h")) {
|
|
188
|
+
windowsSources.push(join("src", "windows", entry));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Add binding.cpp
|
|
194
|
+
const sources = [...windowsSources, "src/binding.cpp"];
|
|
195
|
+
|
|
196
|
+
// Find MSVC include paths
|
|
197
|
+
const msvcPaths = [
|
|
198
|
+
"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\VC\\Tools\\MSVC",
|
|
199
|
+
"C:\\Program Files\\Microsoft Visual Studio\\2022\\Professional\\VC\\Tools\\MSVC",
|
|
200
|
+
"C:\\Program Files\\Microsoft Visual Studio\\2022\\Enterprise\\VC\\Tools\\MSVC",
|
|
201
|
+
"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\BuildTools\\VC\\Tools\\MSVC",
|
|
202
|
+
"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Tools\\MSVC",
|
|
203
|
+
];
|
|
204
|
+
|
|
205
|
+
let msvcInclude = "";
|
|
206
|
+
for (const basePath of msvcPaths) {
|
|
207
|
+
if (existsSync(basePath)) {
|
|
208
|
+
const versions = require("fs").readdirSync(basePath);
|
|
209
|
+
if (versions.length > 0) {
|
|
210
|
+
const version = versions.sort().reverse()[0];
|
|
211
|
+
msvcInclude = join(basePath, version, "include");
|
|
212
|
+
if (existsSync(msvcInclude)) {
|
|
213
|
+
console.log("Found MSVC includes at:", msvcInclude);
|
|
214
|
+
break;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Find Windows SDK paths
|
|
221
|
+
const sdkBase = "C:\\Program Files (x86)\\Windows Kits\\10\\Include";
|
|
222
|
+
let sdkVersion = "";
|
|
223
|
+
if (existsSync(sdkBase)) {
|
|
224
|
+
const versions = require("fs")
|
|
225
|
+
.readdirSync(sdkBase)
|
|
226
|
+
.filter((v) => v.match(/^\d+\.\d+\.\d+\.\d+$/))
|
|
227
|
+
.sort()
|
|
228
|
+
.reverse();
|
|
229
|
+
if (versions.length > 0) {
|
|
230
|
+
sdkVersion = versions[0];
|
|
231
|
+
console.log("Found Windows SDK version:", sdkVersion);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Create compile commands with absolute paths
|
|
236
|
+
const commands = sources.map((source: string) => ({
|
|
237
|
+
directory: process.cwd(),
|
|
238
|
+
file: source,
|
|
239
|
+
command: [
|
|
240
|
+
"clang++", // Use clang++ for clang-tidy compatibility
|
|
241
|
+
"-c",
|
|
242
|
+
source,
|
|
243
|
+
`-I${process.cwd()}/src/windows`,
|
|
244
|
+
`-I${process.cwd()}/node_modules/node-addon-api`,
|
|
245
|
+
`-I${nodeGyp}/include/node`,
|
|
246
|
+
msvcInclude ? `-I${msvcInclude}` : "",
|
|
247
|
+
sdkVersion ? `-I${sdkBase}\\${sdkVersion}\\ucrt` : "",
|
|
248
|
+
sdkVersion ? `-I${sdkBase}\\${sdkVersion}\\shared` : "",
|
|
249
|
+
sdkVersion ? `-I${sdkBase}\\${sdkVersion}\\um` : "",
|
|
250
|
+
sdkVersion ? `-I${sdkBase}\\${sdkVersion}\\winrt` : "",
|
|
251
|
+
"-DWIN32",
|
|
252
|
+
"-D_WINDOWS",
|
|
253
|
+
"-D_WIN64",
|
|
254
|
+
"-D_M_X64=1",
|
|
255
|
+
"-D_AMD64_=1",
|
|
256
|
+
"-DNAPI_VERSION=8",
|
|
257
|
+
"-DNODE_ADDON_API_DISABLE_DEPRECATED",
|
|
258
|
+
"-DBUILDING_NODE_EXTENSION",
|
|
259
|
+
"-std=c++17",
|
|
260
|
+
"-fms-compatibility",
|
|
261
|
+
"-fms-extensions",
|
|
262
|
+
"-Wno-microsoft-include",
|
|
263
|
+
]
|
|
264
|
+
.filter((arg) => arg)
|
|
265
|
+
.join(" "),
|
|
266
|
+
}));
|
|
267
|
+
|
|
268
|
+
writeFileSync("compile_commands.json", JSON.stringify(commands, null, 2));
|
|
269
|
+
console.log(
|
|
270
|
+
`Created compile_commands.json with ${commands.length} entries`,
|
|
271
|
+
);
|
|
272
|
+
return true;
|
|
273
|
+
} catch (error) {
|
|
274
|
+
console.error("Failed to generate compile_commands.json:", error);
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
70
277
|
}
|
|
71
278
|
|
|
72
|
-
// Generate compile_commands.json
|
|
73
|
-
|
|
74
|
-
if (existsSync(compileCommandsPath)) {
|
|
75
|
-
console.log("Using existing compile_commands.json");
|
|
76
|
-
} else {
|
|
279
|
+
// Generate compile_commands.json for Unix-like systems
|
|
280
|
+
async function generateUnixCompileCommands(): Promise<void> {
|
|
77
281
|
console.log("Generating compile_commands.json...");
|
|
78
282
|
|
|
79
|
-
|
|
80
|
-
|
|
283
|
+
if (
|
|
284
|
+
!checkCommand(
|
|
285
|
+
"bear",
|
|
286
|
+
isLinux
|
|
287
|
+
? "sudo apt-get install bear"
|
|
288
|
+
: isMacOS
|
|
289
|
+
? "brew install bear"
|
|
290
|
+
: "see https://github.com/rizsotto/Bear",
|
|
291
|
+
)
|
|
292
|
+
) {
|
|
293
|
+
process.exit(1);
|
|
294
|
+
}
|
|
295
|
+
|
|
81
296
|
execSync("npm run setup:native && bear -- npm run node-gyp-rebuild", {
|
|
82
297
|
stdio: "inherit",
|
|
83
298
|
});
|
|
84
299
|
|
|
85
|
-
|
|
86
|
-
if (!existsSync(compileCommandsPath)) {
|
|
300
|
+
if (!existsSync("compile_commands.json")) {
|
|
87
301
|
console.error("Failed to generate compile_commands.json");
|
|
88
|
-
console.error("Make sure bear is installed: sudo apt-get install bear");
|
|
89
302
|
process.exit(1);
|
|
90
303
|
}
|
|
91
304
|
}
|
|
92
305
|
|
|
93
|
-
//
|
|
94
|
-
function
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
306
|
+
// Get source files to check
|
|
307
|
+
async function getSourceFiles(): Promise<string[]> {
|
|
308
|
+
if (isWindows) {
|
|
309
|
+
// Windows-specific files
|
|
310
|
+
const files: string[] = [];
|
|
311
|
+
const windowsDir = join("src", "windows");
|
|
312
|
+
|
|
313
|
+
if (existsSync(windowsDir)) {
|
|
314
|
+
const entries = require("fs").readdirSync(windowsDir);
|
|
315
|
+
for (const entry of entries) {
|
|
316
|
+
if (entry.endsWith(".cpp") || entry.endsWith(".h")) {
|
|
317
|
+
files.push(join(windowsDir, entry));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
102
320
|
}
|
|
321
|
+
|
|
322
|
+
// Also include binding.cpp
|
|
323
|
+
files.push(join("src", "binding.cpp"));
|
|
324
|
+
return files;
|
|
325
|
+
} else {
|
|
326
|
+
// Platform-specific exclusions for Unix-like systems
|
|
327
|
+
let excludePattern = "";
|
|
328
|
+
if (isMacOS) {
|
|
329
|
+
excludePattern = "| grep -v -E '(windows|linux)/'";
|
|
330
|
+
} else if (isLinux) {
|
|
331
|
+
excludePattern = "| grep -v -E '(windows|darwin)/'";
|
|
332
|
+
} else {
|
|
333
|
+
excludePattern = "| grep -v -E '(windows|darwin|linux)/'";
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const { stdout } = await exec(
|
|
337
|
+
`find src -name '*.cpp' -o -name '*.h' | grep -E '\\.(cpp|h)$' ${excludePattern}`,
|
|
338
|
+
);
|
|
339
|
+
return stdout
|
|
340
|
+
.trim()
|
|
341
|
+
.split("\n")
|
|
342
|
+
.filter((f) => f);
|
|
103
343
|
}
|
|
104
|
-
return "clang-tidy"; // fallback
|
|
105
344
|
}
|
|
106
345
|
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
346
|
+
// Filter out known macOS Homebrew LLVM header issues
|
|
347
|
+
function filterMacOSHeaderErrors(output: string): {
|
|
348
|
+
filteredOutput: string;
|
|
349
|
+
errors: number;
|
|
350
|
+
warnings: number;
|
|
351
|
+
filtered: number;
|
|
352
|
+
} {
|
|
353
|
+
const lines = output.split("\n");
|
|
354
|
+
const filteredLines: string[] = [];
|
|
355
|
+
let errors = 0;
|
|
356
|
+
let warnings = 0;
|
|
357
|
+
let filtered = 0;
|
|
358
|
+
|
|
359
|
+
// Patterns for known Homebrew LLVM vs Apple clang header mismatches
|
|
360
|
+
const knownHeaderErrors = [
|
|
361
|
+
// Standard library headers not found
|
|
362
|
+
/'(functional|chrono|cstring|iostream|string|vector|memory|algorithm|map|set|iterator)' file not found/,
|
|
363
|
+
// Apple framework headers not found when using Homebrew clang-tidy
|
|
364
|
+
/'CoreFoundation\/CoreFoundation\.h' file not found/,
|
|
365
|
+
/'DiskArbitration\/DiskArbitration\.h' file not found/,
|
|
366
|
+
// LLVM-specific errors that don't affect actual compilation
|
|
367
|
+
/unknown type name '_LIBCPP_/,
|
|
368
|
+
/no member named '\w+' in namespace 'std'/,
|
|
369
|
+
/no template named '\w+' in namespace 'std'/,
|
|
370
|
+
];
|
|
371
|
+
|
|
372
|
+
for (const line of lines) {
|
|
373
|
+
let isKnownError = false;
|
|
374
|
+
|
|
375
|
+
// Check if this is a known header error
|
|
376
|
+
if (line.includes(" error:")) {
|
|
377
|
+
for (const pattern of knownHeaderErrors) {
|
|
378
|
+
if (pattern.test(line)) {
|
|
379
|
+
isKnownError = true;
|
|
380
|
+
filtered++;
|
|
381
|
+
break;
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (!isKnownError) {
|
|
387
|
+
filteredLines.push(line);
|
|
388
|
+
|
|
389
|
+
// Count errors and warnings
|
|
390
|
+
if (line.includes(" error:")) {
|
|
391
|
+
errors++;
|
|
392
|
+
}
|
|
393
|
+
if (line.includes(" warning:")) {
|
|
394
|
+
warnings++;
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
return {
|
|
400
|
+
filteredOutput: filteredLines.join("\n"),
|
|
401
|
+
errors,
|
|
402
|
+
warnings,
|
|
403
|
+
filtered,
|
|
404
|
+
};
|
|
116
405
|
}
|
|
117
406
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
407
|
+
// Filter out known Windows header issues
|
|
408
|
+
function filterWindowsHeaderErrors(output: string): {
|
|
409
|
+
filteredOutput: string;
|
|
121
410
|
errors: number;
|
|
122
411
|
warnings: number;
|
|
412
|
+
} {
|
|
413
|
+
const lines = output.split("\n");
|
|
414
|
+
const filteredLines: string[] = [];
|
|
415
|
+
let errors = 0;
|
|
416
|
+
let warnings = 0;
|
|
417
|
+
let skipNextLine = false;
|
|
418
|
+
let inSystemHeader = false;
|
|
419
|
+
|
|
420
|
+
// Patterns for known header issues that we want to filter out
|
|
421
|
+
const systemHeaderPatterns = [
|
|
422
|
+
// System header paths - match the entire error line
|
|
423
|
+
/C:\\Program Files.*:\d+:\d+: (error|warning):/,
|
|
424
|
+
/C:\\Users\\.*\\AppData.*:\d+:\d+: (error|warning):/,
|
|
425
|
+
/\.node-gyp.*:\d+:\d+: (error|warning):/,
|
|
426
|
+
/Windows Kits.*:\d+:\d+: (error|warning):/,
|
|
427
|
+
];
|
|
428
|
+
|
|
429
|
+
// Known system header error messages that appear in user files
|
|
430
|
+
const systemHeaderErrors = [
|
|
431
|
+
// File not found errors - these are the most common MSVC header issues
|
|
432
|
+
/'(chrono|functional|windows\.h|iostream|string|vector|memory|algorithm|map|set|unordered_map|iterator|utility|tuple|type_traits|cstddef|cstdint|exception|new|limits|stdexcept)' file not found/,
|
|
433
|
+
/no member named '\w+' in the global namespace/,
|
|
434
|
+
/no member named '\w+' in namespace 'std'/,
|
|
435
|
+
/no template named '\w+' in namespace 'std'/,
|
|
436
|
+
/no template named 'pointer_traits'/,
|
|
437
|
+
/unknown type name 'stream(pos|off)'/,
|
|
438
|
+
/no type named 'string' in namespace 'std'/,
|
|
439
|
+
/use of undeclared identifier '_Elem'/,
|
|
440
|
+
/use of undeclared identifier 'tuple'/,
|
|
441
|
+
/cannot initialize return object of type 'int' with an lvalue of type 'const char/,
|
|
442
|
+
/expected ';' after expression/,
|
|
443
|
+
/unknown type name 'namespace'/,
|
|
444
|
+
/expected unqualified-id/,
|
|
445
|
+
/no type named 'type' in/,
|
|
446
|
+
/declaration of anonymous struct must be a definition/,
|
|
447
|
+
];
|
|
448
|
+
|
|
449
|
+
for (let i = 0; i < lines.length; i++) {
|
|
450
|
+
// eslint-disable-next-line security/detect-object-injection
|
|
451
|
+
const line = lines[i];
|
|
452
|
+
|
|
453
|
+
if (skipNextLine) {
|
|
454
|
+
skipNextLine = false;
|
|
455
|
+
continue;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Check if this is a system header location
|
|
459
|
+
let isSystemHeader = false;
|
|
460
|
+
for (const pattern of systemHeaderPatterns) {
|
|
461
|
+
if (pattern.test(line)) {
|
|
462
|
+
isSystemHeader = true;
|
|
463
|
+
inSystemHeader = true;
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Check if this is a known system header error in a user file
|
|
469
|
+
if (!isSystemHeader && line.includes(" error:")) {
|
|
470
|
+
for (const pattern of systemHeaderErrors) {
|
|
471
|
+
if (pattern.test(line)) {
|
|
472
|
+
isSystemHeader = true;
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Reset inSystemHeader flag when we see a new file
|
|
479
|
+
if (line.match(/^[^:]+\.(cpp|h|hpp):\d+:\d+: (error|warning):/)) {
|
|
480
|
+
inSystemHeader = false;
|
|
481
|
+
for (const pattern of systemHeaderPatterns) {
|
|
482
|
+
if (pattern.test(line)) {
|
|
483
|
+
inSystemHeader = true;
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (!isSystemHeader && !inSystemHeader) {
|
|
490
|
+
// Only add non-header errors to output
|
|
491
|
+
filteredLines.push(line);
|
|
492
|
+
|
|
493
|
+
// Count errors and warnings
|
|
494
|
+
if (line.includes(" error:")) {
|
|
495
|
+
errors++;
|
|
496
|
+
}
|
|
497
|
+
if (line.includes(" warning:")) {
|
|
498
|
+
warnings++;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return {
|
|
504
|
+
filteredOutput: filteredLines.join("\n"),
|
|
505
|
+
errors,
|
|
506
|
+
warnings,
|
|
507
|
+
};
|
|
123
508
|
}
|
|
124
509
|
|
|
125
510
|
// Run clang-tidy on a single file
|
|
126
511
|
async function runClangTidyOnFile(
|
|
127
512
|
clangTidy: string,
|
|
128
513
|
file: string,
|
|
129
|
-
): Promise<
|
|
514
|
+
): Promise<{
|
|
515
|
+
file: string;
|
|
516
|
+
output: string;
|
|
517
|
+
errors: number;
|
|
518
|
+
warnings: number;
|
|
519
|
+
filtered?: number;
|
|
520
|
+
}> {
|
|
130
521
|
try {
|
|
131
|
-
|
|
132
|
-
|
|
522
|
+
let extraArgs = "";
|
|
523
|
+
|
|
524
|
+
// Platform-specific config and arguments
|
|
525
|
+
if (isWindows) {
|
|
526
|
+
// Always use src/windows/.clang-tidy for Windows
|
|
527
|
+
const configPath = join("src", "windows", ".clang-tidy");
|
|
528
|
+
if (existsSync(configPath)) {
|
|
529
|
+
extraArgs = `--config-file=${configPath}`;
|
|
530
|
+
}
|
|
531
|
+
} else if (isMacOS && clangTidy.includes("/opt/")) {
|
|
532
|
+
// macOS with Homebrew LLVM needs extra paths
|
|
533
|
+
// The compile_commands.json uses Apple's /usr/bin/cc, but we're running
|
|
534
|
+
// Homebrew's clang-tidy which needs Homebrew's C++ stdlib
|
|
535
|
+
const sdkPath = execSync("xcrun --show-sdk-path", {
|
|
536
|
+
encoding: "utf8",
|
|
537
|
+
}).trim();
|
|
538
|
+
|
|
539
|
+
// Extract Homebrew prefix from clang-tidy path
|
|
540
|
+
const brewPrefix = clangTidy.includes("/opt/homebrew")
|
|
541
|
+
? "/opt/homebrew"
|
|
542
|
+
: "/usr/local";
|
|
543
|
+
|
|
544
|
+
extraArgs =
|
|
545
|
+
`--extra-arg=-nostdinc++ ` + // Ignore built-in C++ paths
|
|
546
|
+
`--extra-arg=-isystem${brewPrefix}/opt/llvm/include/c++/v1 ` +
|
|
547
|
+
`--extra-arg=-isysroot${sdkPath} ` +
|
|
548
|
+
`--extra-arg=-isystem${sdkPath}/usr/include ` +
|
|
549
|
+
`--extra-arg=-F${sdkPath}/System/Library/Frameworks`;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const { stdout, stderr } = await exec(
|
|
553
|
+
`${isWindows ? `"${clangTidy}"` : clangTidy} -p . ${extraArgs} "${file}" 2>&1`,
|
|
554
|
+
);
|
|
555
|
+
let output = stdout + stderr;
|
|
133
556
|
|
|
134
557
|
let errors = 0;
|
|
135
558
|
let warnings = 0;
|
|
136
|
-
|
|
559
|
+
let filteredCount = 0;
|
|
137
560
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
561
|
+
// Filter platform-specific header errors
|
|
562
|
+
if (isWindows) {
|
|
563
|
+
const filtered = filterWindowsHeaderErrors(output);
|
|
564
|
+
output = filtered.filteredOutput;
|
|
565
|
+
errors = filtered.errors;
|
|
566
|
+
warnings = filtered.warnings;
|
|
567
|
+
} else if (isMacOS && clangTidy.includes("/opt/")) {
|
|
568
|
+
// Filter Homebrew LLVM errors on macOS
|
|
569
|
+
const filtered = filterMacOSHeaderErrors(output);
|
|
570
|
+
output = filtered.filteredOutput;
|
|
571
|
+
errors = filtered.errors;
|
|
572
|
+
warnings = filtered.warnings;
|
|
573
|
+
filteredCount = filtered.filtered;
|
|
574
|
+
} else {
|
|
575
|
+
const lines = output.split("\n");
|
|
576
|
+
for (const line of lines) {
|
|
577
|
+
if (line.includes(" warning:")) warnings++;
|
|
578
|
+
if (line.includes(" error:")) errors++;
|
|
579
|
+
}
|
|
141
580
|
}
|
|
142
581
|
|
|
143
|
-
return { file, output, errors, warnings };
|
|
582
|
+
return { file, output, errors, warnings, filtered: filteredCount };
|
|
144
583
|
} catch (error: any) {
|
|
145
|
-
|
|
146
|
-
const output = error.stdout || error.stderr || error.message;
|
|
584
|
+
let output = error.stdout || error.stderr || error.message;
|
|
147
585
|
let errors = 0;
|
|
148
586
|
let warnings = 0;
|
|
587
|
+
let filteredCount = 0;
|
|
149
588
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
589
|
+
// Filter platform-specific header errors
|
|
590
|
+
if (isWindows) {
|
|
591
|
+
const filtered = filterWindowsHeaderErrors(output);
|
|
592
|
+
output = filtered.filteredOutput;
|
|
593
|
+
errors = filtered.errors;
|
|
594
|
+
warnings = filtered.warnings;
|
|
595
|
+
} else if (isMacOS && clangTidy.includes("/opt/")) {
|
|
596
|
+
// Filter Homebrew LLVM errors on macOS
|
|
597
|
+
const filtered = filterMacOSHeaderErrors(output);
|
|
598
|
+
output = filtered.filteredOutput;
|
|
599
|
+
errors = filtered.errors;
|
|
600
|
+
warnings = filtered.warnings;
|
|
601
|
+
filteredCount = filtered.filtered;
|
|
602
|
+
} else {
|
|
603
|
+
const lines = output.split("\n");
|
|
604
|
+
for (const line of lines) {
|
|
605
|
+
if (line.includes(" warning:")) warnings++;
|
|
606
|
+
if (line.includes(" error:")) errors++;
|
|
607
|
+
}
|
|
154
608
|
}
|
|
155
609
|
|
|
156
|
-
return { file, output, errors, warnings };
|
|
610
|
+
return { file, output, errors, warnings, filtered: filteredCount };
|
|
157
611
|
}
|
|
158
612
|
}
|
|
159
613
|
|
|
160
614
|
// Main function
|
|
161
615
|
async function main(): Promise<void> {
|
|
162
616
|
const clangTidy = findClangTidy();
|
|
617
|
+
|
|
618
|
+
if (!clangTidy) {
|
|
619
|
+
console.error(`${colors.red}Error: clang-tidy not found${colors.reset}`);
|
|
620
|
+
if (isWindows) {
|
|
621
|
+
console.error("\nTo install clang-tidy on Windows:");
|
|
622
|
+
console.error(
|
|
623
|
+
"1. Install LLVM: https://github.com/llvm/llvm-project/releases",
|
|
624
|
+
);
|
|
625
|
+
console.error("2. Or install Visual Studio 2019/2022 with C++ tools");
|
|
626
|
+
} else if (isMacOS) {
|
|
627
|
+
console.error("\nTo install on macOS:");
|
|
628
|
+
console.error(" Option 1: brew install clang-tidy");
|
|
629
|
+
console.error(" Option 2: brew install llvm");
|
|
630
|
+
} else {
|
|
631
|
+
console.error("\nTo install on Linux:");
|
|
632
|
+
console.error(" sudo apt-get install clang-tidy");
|
|
633
|
+
}
|
|
634
|
+
process.exit(1);
|
|
635
|
+
}
|
|
636
|
+
|
|
163
637
|
console.log(`${colors.blue}=== Running clang-tidy ===${colors.reset}`);
|
|
164
638
|
console.log(`${colors.dim}Using: ${clangTidy}${colors.reset}`);
|
|
639
|
+
console.log(`${colors.dim}Platform: ${platform()}${colors.reset}`);
|
|
640
|
+
|
|
641
|
+
// Generate or check compile_commands.json
|
|
642
|
+
if (!existsSync("compile_commands.json")) {
|
|
643
|
+
if (isWindows) {
|
|
644
|
+
if (!(await generateWindowsCompileCommands())) {
|
|
645
|
+
process.exit(1);
|
|
646
|
+
}
|
|
647
|
+
} else {
|
|
648
|
+
await generateUnixCompileCommands();
|
|
649
|
+
}
|
|
650
|
+
} else {
|
|
651
|
+
console.log("Using existing compile_commands.json");
|
|
652
|
+
}
|
|
165
653
|
|
|
166
|
-
// Get files
|
|
654
|
+
// Get files to check
|
|
167
655
|
const files = await getSourceFiles();
|
|
168
656
|
if (files.length === 0) {
|
|
169
657
|
console.log(
|
|
@@ -176,42 +664,103 @@ async function main(): Promise<void> {
|
|
|
176
664
|
`${colors.dim}Checking ${files.length} files...${colors.reset}\n`,
|
|
177
665
|
);
|
|
178
666
|
|
|
179
|
-
// Run clang-tidy on files
|
|
180
|
-
const parallelism = Math.min(cpus().length, 8);
|
|
181
|
-
const results:
|
|
667
|
+
// Run clang-tidy on files
|
|
668
|
+
const parallelism = isWindows ? 1 : Math.min(cpus().length, 8);
|
|
669
|
+
const results: Array<{
|
|
670
|
+
file: string;
|
|
671
|
+
output: string;
|
|
672
|
+
errors: number;
|
|
673
|
+
warnings: number;
|
|
674
|
+
}> = [];
|
|
182
675
|
|
|
183
|
-
// Process files
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
results.push(...chunkResults);
|
|
676
|
+
// Process files
|
|
677
|
+
if (isWindows) {
|
|
678
|
+
// Sequential on Windows to avoid issues
|
|
679
|
+
for (const file of files) {
|
|
680
|
+
const result = await runClangTidyOnFile(clangTidy, file);
|
|
681
|
+
results.push(result);
|
|
190
682
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
683
|
+
// Show progress
|
|
684
|
+
const relPath = file.replace(
|
|
685
|
+
process.cwd() + (isWindows ? "\\" : "/"),
|
|
686
|
+
"",
|
|
687
|
+
);
|
|
194
688
|
if (result.errors > 0) {
|
|
195
689
|
console.log(
|
|
196
690
|
`${colors.red}✗${colors.reset} ${relPath} (${result.errors} errors, ${result.warnings} warnings)`,
|
|
197
691
|
);
|
|
198
|
-
// Show
|
|
692
|
+
// Show first few errors (already filtered on Windows)
|
|
199
693
|
const errorLines = result.output
|
|
200
694
|
.split("\n")
|
|
201
695
|
.filter(
|
|
202
696
|
(line) => line.includes(" error:") || line.includes(" warning:"),
|
|
203
697
|
);
|
|
204
|
-
errorLines
|
|
205
|
-
|
|
206
|
-
|
|
698
|
+
errorLines
|
|
699
|
+
.slice(0, 5)
|
|
700
|
+
.forEach((line) =>
|
|
701
|
+
console.log(` ${colors.dim}${line}${colors.reset}`),
|
|
702
|
+
);
|
|
703
|
+
if (errorLines.length > 5) {
|
|
704
|
+
console.log(
|
|
705
|
+
` ${colors.dim}... and ${errorLines.length - 5} more${colors.reset}`,
|
|
706
|
+
);
|
|
707
|
+
}
|
|
207
708
|
} else if (result.warnings > 0) {
|
|
208
709
|
console.log(
|
|
209
710
|
`${colors.yellow}⚠${colors.reset} ${relPath} (${result.warnings} warnings)`,
|
|
210
711
|
);
|
|
712
|
+
// Show warnings (already filtered on Windows)
|
|
713
|
+
const warningLines = result.output
|
|
714
|
+
.split("\n")
|
|
715
|
+
.filter((line) => line.includes(" warning:"));
|
|
716
|
+
warningLines.forEach((line) =>
|
|
717
|
+
console.log(` ${colors.dim}${line}${colors.reset}`),
|
|
718
|
+
);
|
|
211
719
|
} else {
|
|
212
720
|
console.log(`${colors.green}✓${colors.reset} ${relPath}`);
|
|
213
721
|
}
|
|
214
722
|
}
|
|
723
|
+
} else {
|
|
724
|
+
// Parallel on Unix-like systems
|
|
725
|
+
for (let i = 0; i < files.length; i += parallelism) {
|
|
726
|
+
const chunk = files.slice(i, i + parallelism);
|
|
727
|
+
const chunkResults = await Promise.all(
|
|
728
|
+
chunk.map((file) => runClangTidyOnFile(clangTidy, file)),
|
|
729
|
+
);
|
|
730
|
+
results.push(...chunkResults);
|
|
731
|
+
|
|
732
|
+
// Show progress
|
|
733
|
+
for (const result of chunkResults) {
|
|
734
|
+
const relPath = result.file.replace(process.cwd() + "/", "");
|
|
735
|
+
if (result.errors > 0) {
|
|
736
|
+
console.log(
|
|
737
|
+
`${colors.red}✗${colors.reset} ${relPath} (${result.errors} errors, ${result.warnings} warnings)`,
|
|
738
|
+
);
|
|
739
|
+
// Show actual errors
|
|
740
|
+
const errorLines = result.output
|
|
741
|
+
.split("\n")
|
|
742
|
+
.filter(
|
|
743
|
+
(line) => line.includes(" error:") || line.includes(" warning:"),
|
|
744
|
+
);
|
|
745
|
+
errorLines.forEach((line) =>
|
|
746
|
+
console.log(` ${colors.dim}${line}${colors.reset}`),
|
|
747
|
+
);
|
|
748
|
+
} else if (result.warnings > 0) {
|
|
749
|
+
console.log(
|
|
750
|
+
`${colors.yellow}⚠${colors.reset} ${relPath} (${result.warnings} warnings)`,
|
|
751
|
+
);
|
|
752
|
+
// Show warnings
|
|
753
|
+
const warningLines = result.output
|
|
754
|
+
.split("\n")
|
|
755
|
+
.filter((line) => line.includes(" warning:"));
|
|
756
|
+
warningLines.forEach((line) =>
|
|
757
|
+
console.log(` ${colors.dim}${line}${colors.reset}`),
|
|
758
|
+
);
|
|
759
|
+
} else {
|
|
760
|
+
console.log(`${colors.green}✓${colors.reset} ${relPath}`);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
215
764
|
}
|
|
216
765
|
|
|
217
766
|
// Summary
|
|
@@ -231,6 +780,48 @@ async function main(): Promise<void> {
|
|
|
231
780
|
console.log(`${colors.green}✓ No issues found${colors.reset}`);
|
|
232
781
|
}
|
|
233
782
|
|
|
783
|
+
if (isWindows) {
|
|
784
|
+
console.log(
|
|
785
|
+
`\n${colors.dim}Windows: Using src/windows/.clang-tidy with header error filtering${colors.reset}`,
|
|
786
|
+
);
|
|
787
|
+
console.log(
|
|
788
|
+
`${colors.dim}Note: System header errors are automatically filtered out${colors.reset}`,
|
|
789
|
+
);
|
|
790
|
+
} else if (isMacOS && clangTidy.includes("/opt/")) {
|
|
791
|
+
console.log(
|
|
792
|
+
`\n${colors.dim}macOS: Using Homebrew LLVM clang-tidy with header error filtering${colors.reset}`,
|
|
793
|
+
);
|
|
794
|
+
console.log(
|
|
795
|
+
`${colors.dim}Note: Standard library header errors from toolchain mismatch are filtered out${colors.reset}`,
|
|
796
|
+
);
|
|
797
|
+
|
|
798
|
+
// Show filtering statistics for transparency
|
|
799
|
+
const totalFilesChecked = files.length;
|
|
800
|
+
const totalFilteredErrors = results.reduce(
|
|
801
|
+
(sum, r) => sum + (r.filtered || 0),
|
|
802
|
+
0,
|
|
803
|
+
);
|
|
804
|
+
|
|
805
|
+
if (totalFilteredErrors > 0) {
|
|
806
|
+
console.log(
|
|
807
|
+
`${colors.dim}Filtered ${totalFilteredErrors} known header incompatibility error(s)${colors.reset}`,
|
|
808
|
+
);
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// Sanity check: If we analyzed files and found zero issues after filtering, show validation
|
|
812
|
+
if (totalErrors === 0 && totalWarnings === 0 && totalFilesChecked > 0) {
|
|
813
|
+
if (totalFilteredErrors > 0) {
|
|
814
|
+
console.log(
|
|
815
|
+
`${colors.dim}✓ Verified: ${totalFilesChecked} files analyzed cleanly (${totalFilteredErrors} known issues filtered)${colors.reset}`,
|
|
816
|
+
);
|
|
817
|
+
} else {
|
|
818
|
+
console.log(
|
|
819
|
+
`${colors.dim}✓ Verified: ${totalFilesChecked} files analyzed with no issues${colors.reset}`,
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
234
825
|
process.exit(totalErrors > 0 ? 1 : 0);
|
|
235
826
|
}
|
|
236
827
|
|