apdev-js 0.1.1 → 0.2.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/dist/index.cjs CHANGED
@@ -37,7 +37,11 @@ __export(src_exports, {
37
37
  fileToModule: () => fileToModule,
38
38
  findCycles: () => findCycles,
39
39
  isAllowedChar: () => isAllowedChar,
40
+ isDangerousChar: () => isDangerousChar,
41
+ loadCharset: () => loadCharset,
40
42
  loadConfig: () => loadConfig,
43
+ resolveCharsets: () => resolveCharsets,
44
+ resolvePaths: () => resolvePaths,
41
45
  version: () => version
42
46
  });
43
47
  module.exports = __toCommonJS(src_exports);
@@ -49,80 +53,100 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
49
53
  // src/check-chars.ts
50
54
  var import_node_fs = require("fs");
51
55
  var import_node_path = require("path");
52
- var EMOJI_RANGES = [
53
- [127744, 128511],
54
- // Symbols and Pictographs
55
- [128512, 128591],
56
- // Emoticons
57
- [128640, 128767],
58
- // Transport and Map Symbols
59
- [128896, 129023],
60
- // Geometric Shapes Extended
61
- [129280, 129535],
62
- // Supplemental Symbols and Pictographs
63
- [9728, 9983],
64
- // Miscellaneous Symbols
65
- [9984, 10175]
66
- // Dingbats
67
- ];
68
- var EXTRA_ALLOWED_RANGES = [
69
- [128, 255],
70
- // Latin-1 Supplement
71
- [8192, 8303],
72
- // General Punctuation
73
- [8448, 8527],
74
- // Letterlike Symbols
75
- [8592, 8703],
76
- // Arrows
77
- [8704, 8959],
78
- // Mathematical Operators
79
- [8960, 9215],
80
- // Miscellaneous Technical
81
- [9472, 9599],
82
- // Box Drawing
83
- [9632, 9727],
84
- // Geometric Shapes
85
- [11008, 11263],
86
- // Miscellaneous Symbols and Arrows
87
- [65024, 65039]
88
- // Variation Selectors
89
- ];
90
- var ALL_RANGES = [...EMOJI_RANGES, ...EXTRA_ALLOWED_RANGES];
91
- var DANGEROUS_CODEPOINTS = /* @__PURE__ */ new Map([
92
- // Bidi control characters (Trojan Source - CVE-2021-42574)
93
- [8234, "LEFT-TO-RIGHT EMBEDDING"],
94
- [8235, "RIGHT-TO-LEFT EMBEDDING"],
95
- [8236, "POP DIRECTIONAL FORMATTING"],
96
- [8237, "LEFT-TO-RIGHT OVERRIDE"],
97
- [8238, "RIGHT-TO-LEFT OVERRIDE"],
98
- [8294, "LEFT-TO-RIGHT ISOLATE"],
99
- [8295, "RIGHT-TO-LEFT ISOLATE"],
100
- [8296, "FIRST STRONG ISOLATE"],
101
- [8297, "POP DIRECTIONAL ISOLATE"],
102
- // Zero-width characters
103
- [8203, "ZERO WIDTH SPACE"],
104
- [8204, "ZERO WIDTH NON-JOINER"],
105
- [8205, "ZERO WIDTH JOINER"],
106
- [8206, "LEFT-TO-RIGHT MARK"],
107
- [8207, "RIGHT-TO-LEFT MARK"],
108
- [8288, "WORD JOINER"]
109
- ]);
110
- var PYTHON_SUFFIXES = /* @__PURE__ */ new Set([".py"]);
111
- var JS_SUFFIXES = /* @__PURE__ */ new Set([".js", ".ts", ".tsx", ".jsx", ".mjs", ".cjs"]);
112
- function isAllowedChar(c) {
113
- const code = c.codePointAt(0);
114
- if (code <= 127) {
115
- return true;
56
+ var import_node_url = require("url");
57
+ function getCharsetsDir() {
58
+ const thisDir = typeof __dirname !== "undefined" ? __dirname : (0, import_node_path.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
59
+ const devPath = (0, import_node_path.join)(thisDir, "charsets");
60
+ if ((0, import_node_fs.existsSync)(devPath)) {
61
+ return devPath;
62
+ }
63
+ return (0, import_node_path.join)(thisDir, "..", "src", "charsets");
64
+ }
65
+ function loadCharset(nameOrPath) {
66
+ if (nameOrPath.includes(import_node_path.sep) || nameOrPath.includes("/") || nameOrPath.endsWith(".json")) {
67
+ if (!(0, import_node_fs.existsSync)(nameOrPath)) {
68
+ throw new Error(`Charset file not found: ${nameOrPath}`);
69
+ }
70
+ return JSON.parse((0, import_node_fs.readFileSync)(nameOrPath, "utf-8"));
71
+ }
72
+ const filePath = (0, import_node_path.join)(getCharsetsDir(), `${nameOrPath}.json`);
73
+ if (!(0, import_node_fs.existsSync)(filePath)) {
74
+ throw new Error(`Unknown charset: ${nameOrPath}`);
75
+ }
76
+ return JSON.parse((0, import_node_fs.readFileSync)(filePath, "utf-8"));
77
+ }
78
+ function parseRanges(entries) {
79
+ return entries.map((e) => [parseInt(e.start, 16), parseInt(e.end, 16)]);
80
+ }
81
+ function parseDangerous(entries) {
82
+ const map = /* @__PURE__ */ new Map();
83
+ for (const e of entries) {
84
+ map.set(parseInt(e.code, 16), e.name);
85
+ }
86
+ return map;
87
+ }
88
+ function resolveCharsets(charsetNames, charsetFiles) {
89
+ const base = loadCharset("base");
90
+ const rangesSet = /* @__PURE__ */ new Map();
91
+ const dangerous = parseDangerous(base.dangerous ?? []);
92
+ function addRanges(entries) {
93
+ for (const [s, e] of parseRanges(entries)) {
94
+ rangesSet.set(`${s}-${e}`, [s, e]);
95
+ }
96
+ }
97
+ addRanges(base.emoji_ranges ?? []);
98
+ addRanges(base.extra_ranges ?? []);
99
+ for (const name of charsetNames) {
100
+ const data = loadCharset(name);
101
+ addRanges(data.emoji_ranges ?? []);
102
+ addRanges(data.extra_ranges ?? []);
103
+ if (data.dangerous) {
104
+ for (const [code, dname] of parseDangerous(data.dangerous)) {
105
+ dangerous.set(code, dname);
106
+ }
107
+ }
116
108
  }
117
- for (const [start, end] of ALL_RANGES) {
118
- if (code >= start && code <= end) {
119
- return true;
109
+ for (const path of charsetFiles) {
110
+ const data = loadCharset(path);
111
+ addRanges(data.emoji_ranges ?? []);
112
+ addRanges(data.extra_ranges ?? []);
113
+ if (data.dangerous) {
114
+ for (const [code, dname] of parseDangerous(data.dangerous)) {
115
+ dangerous.set(code, dname);
116
+ }
120
117
  }
121
118
  }
119
+ const ranges = [...rangesSet.values()].sort((a, b) => a[0] - b[0]);
120
+ return { ranges, dangerous };
121
+ }
122
+ var PYTHON_SUFFIXES = /* @__PURE__ */ new Set([".py"]);
123
+ var JS_SUFFIXES = /* @__PURE__ */ new Set([".js", ".ts", ".tsx", ".jsx", ".mjs", ".cjs"]);
124
+ function isInRanges(code, ranges) {
125
+ if (code <= 127) return true;
126
+ for (const [start, end] of ranges) {
127
+ if (code >= start && code <= end) return true;
128
+ }
122
129
  return false;
123
130
  }
131
+ var _baseRanges = null;
132
+ var _baseDangerous = null;
133
+ function getBaseDefaults() {
134
+ if (!_baseRanges || !_baseDangerous) {
135
+ const defaults = resolveCharsets([], []);
136
+ _baseRanges = defaults.ranges;
137
+ _baseDangerous = defaults.dangerous;
138
+ }
139
+ return { ranges: _baseRanges, dangerous: _baseDangerous };
140
+ }
141
+ function isAllowedChar(c) {
142
+ const { ranges, dangerous } = getBaseDefaults();
143
+ const code = c.codePointAt(0);
144
+ if (dangerous.has(code)) return false;
145
+ return isInRanges(code, ranges);
146
+ }
124
147
  function isDangerousChar(c) {
125
- return DANGEROUS_CODEPOINTS.has(c.codePointAt(0));
148
+ const { dangerous } = getBaseDefaults();
149
+ return dangerous.has(c.codePointAt(0));
126
150
  }
127
151
  function computeCommentMask(content, suffix) {
128
152
  if (PYTHON_SUFFIXES.has(suffix)) {
@@ -241,8 +265,13 @@ function computeCommentMaskJs(content) {
241
265
  }
242
266
  return mask;
243
267
  }
244
- function checkFile(filePath, maxProblems = 5) {
268
+ function checkFile(filePath, maxProblems = 5, extraRanges, dangerousMap) {
245
269
  const problems = [];
270
+ if (!extraRanges || !dangerousMap) {
271
+ const defaults = getBaseDefaults();
272
+ extraRanges ??= defaults.ranges;
273
+ dangerousMap ??= defaults.dangerous;
274
+ }
246
275
  try {
247
276
  const content = (0, import_node_fs.readFileSync)(filePath, "utf-8");
248
277
  const suffix = (0, import_node_path.extname)(filePath).toLowerCase();
@@ -252,15 +281,15 @@ function checkFile(filePath, maxProblems = 5) {
252
281
  for (const char of content) {
253
282
  position++;
254
283
  const code = char.codePointAt(0);
255
- if (isDangerousChar(char)) {
284
+ if (dangerousMap.has(code)) {
256
285
  if (!commentMask.has(offset)) {
257
- const name = DANGEROUS_CODEPOINTS.get(code);
286
+ const name = dangerousMap.get(code);
258
287
  const hex = code.toString(16).toUpperCase().padStart(4, "0");
259
288
  problems.push(
260
289
  `Dangerous character in code at position ${position}: U+${hex} (${name})`
261
290
  );
262
291
  }
263
- } else if (!isAllowedChar(char)) {
292
+ } else if (!isInRanges(code, extraRanges)) {
264
293
  const hex = code.toString(16).toUpperCase().padStart(4, "0");
265
294
  problems.push(
266
295
  `Illegal character at position ${position}: ${JSON.stringify(char)} (U+${hex})`
@@ -271,15 +300,169 @@ function checkFile(filePath, maxProblems = 5) {
271
300
  }
272
301
  offset += char.length;
273
302
  }
274
- } catch {
275
- problems.push(`Failed to read file: ${filePath}`);
303
+ } catch (e) {
304
+ problems.push(`Failed to read file: ${filePath} (${e})`);
276
305
  }
277
306
  return problems;
278
307
  }
279
- function checkPaths(paths) {
308
+ var SKIP_SUFFIXES = /* @__PURE__ */ new Set([
309
+ // Bytecode
310
+ ".pyc",
311
+ ".pyo",
312
+ // Images
313
+ ".png",
314
+ ".jpg",
315
+ ".jpeg",
316
+ ".gif",
317
+ ".bmp",
318
+ ".ico",
319
+ ".svg",
320
+ ".webp",
321
+ // Fonts
322
+ ".ttf",
323
+ ".otf",
324
+ ".woff",
325
+ ".woff2",
326
+ ".eot",
327
+ // Archives
328
+ ".zip",
329
+ ".tar",
330
+ ".gz",
331
+ ".bz2",
332
+ ".xz",
333
+ ".7z",
334
+ // Compiled / binary
335
+ ".so",
336
+ ".dylib",
337
+ ".dll",
338
+ ".exe",
339
+ ".o",
340
+ ".a",
341
+ ".whl",
342
+ ".egg",
343
+ // Media
344
+ ".mp3",
345
+ ".mp4",
346
+ ".wav",
347
+ ".avi",
348
+ ".mov",
349
+ ".flac",
350
+ ".ogg",
351
+ // Documents
352
+ ".pdf",
353
+ ".doc",
354
+ ".docx",
355
+ ".xls",
356
+ ".xlsx",
357
+ ".ppt",
358
+ ".pptx",
359
+ // Data
360
+ ".db",
361
+ ".sqlite",
362
+ ".sqlite3",
363
+ ".pickle",
364
+ ".pkl"
365
+ ]);
366
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
367
+ "__pycache__",
368
+ "node_modules",
369
+ ".git",
370
+ ".venv",
371
+ "venv",
372
+ ".tox",
373
+ ".mypy_cache",
374
+ ".pytest_cache",
375
+ ".ruff_cache",
376
+ "dist",
377
+ "build"
378
+ ]);
379
+ var DEFAULT_DIRS = ["src", "tests", "examples"];
380
+ var DEFAULT_GLOBS = ["*.md", "*.yml", "*.yaml", "*.json", ".gitignore"];
381
+ function walkDir(directory) {
382
+ const files = [];
383
+ let entries;
384
+ try {
385
+ entries = (0, import_node_fs.readdirSync)(directory).sort();
386
+ } catch {
387
+ return files;
388
+ }
389
+ for (const name of entries) {
390
+ if (name.startsWith(".")) continue;
391
+ const fullPath = (0, import_node_path.join)(directory, name);
392
+ let stat;
393
+ try {
394
+ stat = (0, import_node_fs.statSync)(fullPath);
395
+ } catch {
396
+ continue;
397
+ }
398
+ if (stat.isDirectory()) {
399
+ if (SKIP_DIRS.has(name) || name.endsWith(".egg-info")) continue;
400
+ files.push(...walkDir(fullPath));
401
+ } else if (stat.isFile()) {
402
+ if (SKIP_SUFFIXES.has((0, import_node_path.extname)(name).toLowerCase())) continue;
403
+ files.push(fullPath);
404
+ }
405
+ }
406
+ return files;
407
+ }
408
+ function defaultProjectFiles() {
409
+ const cwd = process.cwd();
410
+ const files = [];
411
+ for (const dirname3 of DEFAULT_DIRS) {
412
+ const d = (0, import_node_path.join)(cwd, dirname3);
413
+ if ((0, import_node_fs.existsSync)(d) && (0, import_node_fs.statSync)(d).isDirectory()) {
414
+ files.push(...walkDir(d));
415
+ }
416
+ }
417
+ for (const pattern of DEFAULT_GLOBS) {
418
+ if (pattern.startsWith("*.")) {
419
+ const suffix = pattern.slice(1);
420
+ try {
421
+ for (const name of (0, import_node_fs.readdirSync)(cwd).sort()) {
422
+ if (name.endsWith(suffix) && (0, import_node_fs.statSync)((0, import_node_path.join)(cwd, name)).isFile()) {
423
+ files.push((0, import_node_path.join)(cwd, name));
424
+ }
425
+ }
426
+ } catch {
427
+ }
428
+ } else {
429
+ const fullPath = (0, import_node_path.join)(cwd, pattern);
430
+ if ((0, import_node_fs.existsSync)(fullPath) && (0, import_node_fs.statSync)(fullPath).isFile()) {
431
+ files.push(fullPath);
432
+ }
433
+ }
434
+ }
435
+ return files;
436
+ }
437
+ function resolvePaths(paths) {
438
+ if (paths.length === 0) {
439
+ return defaultProjectFiles();
440
+ }
441
+ const result = [];
442
+ for (const p of paths) {
443
+ try {
444
+ if ((0, import_node_fs.statSync)(p).isDirectory()) {
445
+ result.push(...walkDir(p));
446
+ } else {
447
+ result.push(p);
448
+ }
449
+ } catch {
450
+ result.push(p);
451
+ }
452
+ }
453
+ return result;
454
+ }
455
+ function checkPaths(paths, extraRanges, dangerousMap) {
456
+ const resolved = resolvePaths(paths);
457
+ if (resolved.length === 0) {
458
+ console.log("No files to check.");
459
+ return 0;
460
+ }
280
461
  let hasError = false;
281
- for (const path of paths) {
282
- const problems = checkFile(path);
462
+ let checked = 0;
463
+ for (const path of resolved) {
464
+ const problems = checkFile(path, 5, extraRanges, dangerousMap);
465
+ checked++;
283
466
  if (problems.length > 0) {
284
467
  hasError = true;
285
468
  console.log(`
@@ -289,6 +472,9 @@ ${path} contains illegal characters:`);
289
472
  }
290
473
  }
291
474
  }
475
+ if (!hasError) {
476
+ console.log(`All ${checked} files passed.`);
477
+ }
292
478
  return hasError ? 1 : 0;
293
479
  }
294
480
 
@@ -525,9 +711,9 @@ function loadConfig(projectDir) {
525
711
  // src/index.ts
526
712
  var import_node_fs4 = require("fs");
527
713
  var import_node_path4 = require("path");
528
- var import_node_url = require("url");
714
+ var import_node_url2 = require("url");
529
715
  function readVersion() {
530
- const thisDir = typeof __dirname !== "undefined" ? __dirname : (0, import_node_path4.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
716
+ const thisDir = typeof __dirname !== "undefined" ? __dirname : (0, import_node_path4.dirname)((0, import_node_url2.fileURLToPath)(importMetaUrl));
531
717
  try {
532
718
  const pkg = JSON.parse((0, import_node_fs4.readFileSync)((0, import_node_path4.join)(thisDir, "..", "package.json"), "utf-8"));
533
719
  return pkg.version ?? "0.0.0";
@@ -545,6 +731,10 @@ var version = readVersion();
545
731
  fileToModule,
546
732
  findCycles,
547
733
  isAllowedChar,
734
+ isDangerousChar,
735
+ loadCharset,
548
736
  loadConfig,
737
+ resolveCharsets,
738
+ resolvePaths,
549
739
  version
550
740
  });
package/dist/index.d.cts CHANGED
@@ -1,14 +1,52 @@
1
- /** Return true if the character is in the allowed set. */
1
+ /**
2
+ * Character validation tool.
3
+ *
4
+ * Checks that files contain only allowed characters: ASCII, common emoji,
5
+ * and standard technical symbols (arrows, box-drawing, math operators, etc.).
6
+ *
7
+ * Additionally flags dangerous invisible/bidi characters in code regions
8
+ * (Trojan Source - CVE-2021-42574) while allowing them in comments.
9
+ */
10
+ interface RangeEntry {
11
+ start: string;
12
+ end: string;
13
+ name: string;
14
+ }
15
+ interface DangerousEntry {
16
+ code: string;
17
+ name: string;
18
+ }
19
+ interface CharsetData {
20
+ name: string;
21
+ description?: string;
22
+ emoji_ranges?: RangeEntry[];
23
+ extra_ranges?: RangeEntry[];
24
+ dangerous?: DangerousEntry[];
25
+ }
26
+ declare function loadCharset(nameOrPath: string): CharsetData;
27
+ declare function resolveCharsets(charsetNames: string[], charsetFiles: string[]): {
28
+ ranges: [number, number][];
29
+ dangerous: Map<number, string>;
30
+ };
31
+ /**
32
+ * Return true if the character is in the base allowed set.
33
+ *
34
+ * Dangerous codepoints (Trojan Source vectors) are excluded even though
35
+ * they fall within allowed Unicode ranges.
36
+ */
2
37
  declare function isAllowedChar(c: string): boolean;
38
+ /** Return true if the character is a dangerous invisible/bidi codepoint. */
39
+ declare function isDangerousChar(c: string): boolean;
3
40
  /**
4
41
  * Check a single file for illegal characters.
5
42
  * Returns a list of problem descriptions (empty if the file is clean).
6
43
  */
7
- declare function checkFile(filePath: string, maxProblems?: number): string[];
44
+ declare function checkFile(filePath: string, maxProblems?: number, extraRanges?: [number, number][], dangerousMap?: Map<number, string>): string[];
45
+ declare function resolvePaths(paths: string[]): string[];
8
46
  /**
9
47
  * Check multiple files. Returns 0 if all clean, 1 if any have problems.
10
48
  */
11
- declare function checkPaths(paths: string[]): number;
49
+ declare function checkPaths(paths: string[], extraRanges?: [number, number][], dangerousMap?: Map<number, string>): number;
12
50
 
13
51
  /**
14
52
  * Circular import detection tool.
@@ -38,4 +76,4 @@ declare function loadConfig(projectDir?: string): Record<string, unknown>;
38
76
 
39
77
  declare const version: string;
40
78
 
41
- export { buildDependencyGraph, checkCircularImports, checkFile, checkPaths, fileToModule, findCycles, isAllowedChar, loadConfig, version };
79
+ export { buildDependencyGraph, checkCircularImports, checkFile, checkPaths, fileToModule, findCycles, isAllowedChar, isDangerousChar, loadCharset, loadConfig, resolveCharsets, resolvePaths, version };
package/dist/index.d.ts CHANGED
@@ -1,14 +1,52 @@
1
- /** Return true if the character is in the allowed set. */
1
+ /**
2
+ * Character validation tool.
3
+ *
4
+ * Checks that files contain only allowed characters: ASCII, common emoji,
5
+ * and standard technical symbols (arrows, box-drawing, math operators, etc.).
6
+ *
7
+ * Additionally flags dangerous invisible/bidi characters in code regions
8
+ * (Trojan Source - CVE-2021-42574) while allowing them in comments.
9
+ */
10
+ interface RangeEntry {
11
+ start: string;
12
+ end: string;
13
+ name: string;
14
+ }
15
+ interface DangerousEntry {
16
+ code: string;
17
+ name: string;
18
+ }
19
+ interface CharsetData {
20
+ name: string;
21
+ description?: string;
22
+ emoji_ranges?: RangeEntry[];
23
+ extra_ranges?: RangeEntry[];
24
+ dangerous?: DangerousEntry[];
25
+ }
26
+ declare function loadCharset(nameOrPath: string): CharsetData;
27
+ declare function resolveCharsets(charsetNames: string[], charsetFiles: string[]): {
28
+ ranges: [number, number][];
29
+ dangerous: Map<number, string>;
30
+ };
31
+ /**
32
+ * Return true if the character is in the base allowed set.
33
+ *
34
+ * Dangerous codepoints (Trojan Source vectors) are excluded even though
35
+ * they fall within allowed Unicode ranges.
36
+ */
2
37
  declare function isAllowedChar(c: string): boolean;
38
+ /** Return true if the character is a dangerous invisible/bidi codepoint. */
39
+ declare function isDangerousChar(c: string): boolean;
3
40
  /**
4
41
  * Check a single file for illegal characters.
5
42
  * Returns a list of problem descriptions (empty if the file is clean).
6
43
  */
7
- declare function checkFile(filePath: string, maxProblems?: number): string[];
44
+ declare function checkFile(filePath: string, maxProblems?: number, extraRanges?: [number, number][], dangerousMap?: Map<number, string>): string[];
45
+ declare function resolvePaths(paths: string[]): string[];
8
46
  /**
9
47
  * Check multiple files. Returns 0 if all clean, 1 if any have problems.
10
48
  */
11
- declare function checkPaths(paths: string[]): number;
49
+ declare function checkPaths(paths: string[], extraRanges?: [number, number][], dangerousMap?: Map<number, string>): number;
12
50
 
13
51
  /**
14
52
  * Circular import detection tool.
@@ -38,4 +76,4 @@ declare function loadConfig(projectDir?: string): Record<string, unknown>;
38
76
 
39
77
  declare const version: string;
40
78
 
41
- export { buildDependencyGraph, checkCircularImports, checkFile, checkPaths, fileToModule, findCycles, isAllowedChar, loadConfig, version };
79
+ export { buildDependencyGraph, checkCircularImports, checkFile, checkPaths, fileToModule, findCycles, isAllowedChar, isDangerousChar, loadCharset, loadConfig, resolveCharsets, resolvePaths, version };