-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlint-encoding.ts
More file actions
166 lines (149 loc) · 4.26 KB
/
Copy pathlint-encoding.ts
File metadata and controls
166 lines (149 loc) · 4.26 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#!/usr/bin/env tsx
/**
* check-encoding.ts — Check file encoding and line endings for UTF-8 (no BOM) + LF.
*
* Usage: npx tsx scripts/lint-encoding.ts
*/
import { readFileSync, readdirSync, statSync } from "fs";
import { join, extname } from "path";
const CWD = process.cwd().replace(/\\/g, "/");
const SKIP_DIRS = new Set(["node_modules", ".git", "dist"]);
const SKIP_EXTS = new Set([".db", ".sqlite"]);
const SKIP_FILES = new Set(["data.sqlite"]);
const SCAN_DIRS = ["src", "docs", "tools"];
const SCAN_ROOT_EXTS = new Set([".md", ".json"]);
interface FileIssue {
file_path: string;
issues: string[];
}
function should_skip(name: string): boolean {
if (SKIP_DIRS.has(name)) return true;
if (SKIP_FILES.has(name)) return true;
if (SKIP_EXTS.has(extname(name).toLowerCase())) return true;
return false;
}
function collect_files(dir: string): string[] {
const files: string[] = [];
let entries: string[];
try {
entries = readdirSync(dir);
} catch {
return files;
}
for (const entry of entries) {
if (should_skip(entry)) continue;
const full_path = join(dir, entry);
let stat;
try {
stat = statSync(full_path);
} catch {
continue;
}
if (stat.isDirectory()) {
files.push(...collect_files(full_path));
} else if (stat.isFile()) {
files.push(full_path);
}
}
return files;
}
function check_file(file_path: string): string[] {
const errors: string[] = [];
let buffer: Buffer;
try {
buffer = readFileSync(file_path);
} catch {
errors.push("Cannot read file");
return errors;
}
// Check BOM (UTF-8 BOM = EF BB BF)
if (
buffer.length >= 3 &&
buffer[0] === 0xef &&
buffer[1] === 0xbb &&
buffer[2] === 0xbf
) {
errors.push("UTF-8 BOM detected");
}
// Try decode as UTF-8 and check CRLF
try {
const content = buffer.toString("utf-8");
if (content.includes("\r\n")) {
const lines = content.split("\n");
for (let i = 0; i < lines.length; i++) {
if (lines[i].endsWith("\r")) {
errors.push(`CRLF line ending at line ${i + 1}`);
break;
}
}
}
} catch {
errors.push("Not valid UTF-8 (corrupted)");
}
return errors;
}
// ── Main ───────────────────────────────────────────────────────────────────
const all_issues: FileIssue[] = [];
let total_files = 0;
// Scan directories
for (const dir_name of SCAN_DIRS) {
const dir_path = join(CWD, dir_name);
try {
if (statSync(dir_path).isDirectory()) {
const files = collect_files(dir_path);
for (const f of files) {
total_files++;
const errors = check_file(f);
if (errors.length > 0) {
const normalized = f.replace(/\\/g, "/");
const rel_path = normalized.replace(CWD + "/", "");
all_issues.push({ file_path: rel_path, issues: errors });
}
}
}
} catch {
// skip if dir doesn"t exist
}
}
// Scan root .md and .json files
let root_entries: string[];
try {
root_entries = readdirSync(CWD);
} catch {
root_entries = [];
}
for (const entry of root_entries) {
if (should_skip(entry)) continue;
if (!SCAN_ROOT_EXTS.has(extname(entry).toLowerCase())) continue;
const full_path = join(CWD, entry);
try {
if (statSync(full_path).isFile()) {
total_files++;
const errors = check_file(full_path);
if (errors.length > 0) {
all_issues.push({ file_path: entry, issues: errors });
}
}
} catch {
// skip
}
}
// ── Report ─────────────────────────────────────────────────────────────────
if (all_issues.length === 0) {
console.log(
`[PASS] Encoding check passed. ${total_files} files scanned, 0 issues.`
);
process.exit(0);
}
console.log("[FAIL] Encoding issues found:");
let total_issues = 0;
for (const { file_path, issues } of all_issues) {
for (const issue of issues) {
console.log(` ${file_path}: ${issue}`);
total_issues++;
}
}
console.log(
`${total_files} files scanned, ${total_issues} issues in ${all_issues.length} files.`
);
process.exit(1);