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.js CHANGED
@@ -6,82 +6,102 @@ var getDirname = () => path.dirname(getFilename());
6
6
  var __dirname = /* @__PURE__ */ getDirname();
7
7
 
8
8
  // src/check-chars.ts
9
- import { readFileSync } from "fs";
10
- import { extname } from "path";
11
- var EMOJI_RANGES = [
12
- [127744, 128511],
13
- // Symbols and Pictographs
14
- [128512, 128591],
15
- // Emoticons
16
- [128640, 128767],
17
- // Transport and Map Symbols
18
- [128896, 129023],
19
- // Geometric Shapes Extended
20
- [129280, 129535],
21
- // Supplemental Symbols and Pictographs
22
- [9728, 9983],
23
- // Miscellaneous Symbols
24
- [9984, 10175]
25
- // Dingbats
26
- ];
27
- var EXTRA_ALLOWED_RANGES = [
28
- [128, 255],
29
- // Latin-1 Supplement
30
- [8192, 8303],
31
- // General Punctuation
32
- [8448, 8527],
33
- // Letterlike Symbols
34
- [8592, 8703],
35
- // Arrows
36
- [8704, 8959],
37
- // Mathematical Operators
38
- [8960, 9215],
39
- // Miscellaneous Technical
40
- [9472, 9599],
41
- // Box Drawing
42
- [9632, 9727],
43
- // Geometric Shapes
44
- [11008, 11263],
45
- // Miscellaneous Symbols and Arrows
46
- [65024, 65039]
47
- // Variation Selectors
48
- ];
49
- var ALL_RANGES = [...EMOJI_RANGES, ...EXTRA_ALLOWED_RANGES];
50
- var DANGEROUS_CODEPOINTS = /* @__PURE__ */ new Map([
51
- // Bidi control characters (Trojan Source - CVE-2021-42574)
52
- [8234, "LEFT-TO-RIGHT EMBEDDING"],
53
- [8235, "RIGHT-TO-LEFT EMBEDDING"],
54
- [8236, "POP DIRECTIONAL FORMATTING"],
55
- [8237, "LEFT-TO-RIGHT OVERRIDE"],
56
- [8238, "RIGHT-TO-LEFT OVERRIDE"],
57
- [8294, "LEFT-TO-RIGHT ISOLATE"],
58
- [8295, "RIGHT-TO-LEFT ISOLATE"],
59
- [8296, "FIRST STRONG ISOLATE"],
60
- [8297, "POP DIRECTIONAL ISOLATE"],
61
- // Zero-width characters
62
- [8203, "ZERO WIDTH SPACE"],
63
- [8204, "ZERO WIDTH NON-JOINER"],
64
- [8205, "ZERO WIDTH JOINER"],
65
- [8206, "LEFT-TO-RIGHT MARK"],
66
- [8207, "RIGHT-TO-LEFT MARK"],
67
- [8288, "WORD JOINER"]
68
- ]);
69
- var PYTHON_SUFFIXES = /* @__PURE__ */ new Set([".py"]);
70
- var JS_SUFFIXES = /* @__PURE__ */ new Set([".js", ".ts", ".tsx", ".jsx", ".mjs", ".cjs"]);
71
- function isAllowedChar(c) {
72
- const code = c.codePointAt(0);
73
- if (code <= 127) {
74
- return true;
9
+ import { readFileSync, existsSync, readdirSync, statSync } from "fs";
10
+ import { extname, dirname, join, sep } from "path";
11
+ import { fileURLToPath as fileURLToPath2 } from "url";
12
+ function getCharsetsDir() {
13
+ const thisDir = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath2(import.meta.url));
14
+ const devPath = join(thisDir, "charsets");
15
+ if (existsSync(devPath)) {
16
+ return devPath;
17
+ }
18
+ return join(thisDir, "..", "src", "charsets");
19
+ }
20
+ function loadCharset(nameOrPath) {
21
+ if (nameOrPath.includes(sep) || nameOrPath.includes("/") || nameOrPath.endsWith(".json")) {
22
+ if (!existsSync(nameOrPath)) {
23
+ throw new Error(`Charset file not found: ${nameOrPath}`);
24
+ }
25
+ return JSON.parse(readFileSync(nameOrPath, "utf-8"));
26
+ }
27
+ const filePath = join(getCharsetsDir(), `${nameOrPath}.json`);
28
+ if (!existsSync(filePath)) {
29
+ throw new Error(`Unknown charset: ${nameOrPath}`);
30
+ }
31
+ return JSON.parse(readFileSync(filePath, "utf-8"));
32
+ }
33
+ function parseRanges(entries) {
34
+ return entries.map((e) => [parseInt(e.start, 16), parseInt(e.end, 16)]);
35
+ }
36
+ function parseDangerous(entries) {
37
+ const map = /* @__PURE__ */ new Map();
38
+ for (const e of entries) {
39
+ map.set(parseInt(e.code, 16), e.name);
75
40
  }
76
- for (const [start, end] of ALL_RANGES) {
77
- if (code >= start && code <= end) {
78
- return true;
41
+ return map;
42
+ }
43
+ function resolveCharsets(charsetNames, charsetFiles) {
44
+ const base = loadCharset("base");
45
+ const rangesSet = /* @__PURE__ */ new Map();
46
+ const dangerous = parseDangerous(base.dangerous ?? []);
47
+ function addRanges(entries) {
48
+ for (const [s, e] of parseRanges(entries)) {
49
+ rangesSet.set(`${s}-${e}`, [s, e]);
79
50
  }
80
51
  }
52
+ addRanges(base.emoji_ranges ?? []);
53
+ addRanges(base.extra_ranges ?? []);
54
+ for (const name of charsetNames) {
55
+ const data = loadCharset(name);
56
+ addRanges(data.emoji_ranges ?? []);
57
+ addRanges(data.extra_ranges ?? []);
58
+ if (data.dangerous) {
59
+ for (const [code, dname] of parseDangerous(data.dangerous)) {
60
+ dangerous.set(code, dname);
61
+ }
62
+ }
63
+ }
64
+ for (const path2 of charsetFiles) {
65
+ const data = loadCharset(path2);
66
+ addRanges(data.emoji_ranges ?? []);
67
+ addRanges(data.extra_ranges ?? []);
68
+ if (data.dangerous) {
69
+ for (const [code, dname] of parseDangerous(data.dangerous)) {
70
+ dangerous.set(code, dname);
71
+ }
72
+ }
73
+ }
74
+ const ranges = [...rangesSet.values()].sort((a, b) => a[0] - b[0]);
75
+ return { ranges, dangerous };
76
+ }
77
+ var PYTHON_SUFFIXES = /* @__PURE__ */ new Set([".py"]);
78
+ var JS_SUFFIXES = /* @__PURE__ */ new Set([".js", ".ts", ".tsx", ".jsx", ".mjs", ".cjs"]);
79
+ function isInRanges(code, ranges) {
80
+ if (code <= 127) return true;
81
+ for (const [start, end] of ranges) {
82
+ if (code >= start && code <= end) return true;
83
+ }
81
84
  return false;
82
85
  }
86
+ var _baseRanges = null;
87
+ var _baseDangerous = null;
88
+ function getBaseDefaults() {
89
+ if (!_baseRanges || !_baseDangerous) {
90
+ const defaults = resolveCharsets([], []);
91
+ _baseRanges = defaults.ranges;
92
+ _baseDangerous = defaults.dangerous;
93
+ }
94
+ return { ranges: _baseRanges, dangerous: _baseDangerous };
95
+ }
96
+ function isAllowedChar(c) {
97
+ const { ranges, dangerous } = getBaseDefaults();
98
+ const code = c.codePointAt(0);
99
+ if (dangerous.has(code)) return false;
100
+ return isInRanges(code, ranges);
101
+ }
83
102
  function isDangerousChar(c) {
84
- return DANGEROUS_CODEPOINTS.has(c.codePointAt(0));
103
+ const { dangerous } = getBaseDefaults();
104
+ return dangerous.has(c.codePointAt(0));
85
105
  }
86
106
  function computeCommentMask(content, suffix) {
87
107
  if (PYTHON_SUFFIXES.has(suffix)) {
@@ -200,8 +220,13 @@ function computeCommentMaskJs(content) {
200
220
  }
201
221
  return mask;
202
222
  }
203
- function checkFile(filePath, maxProblems = 5) {
223
+ function checkFile(filePath, maxProblems = 5, extraRanges, dangerousMap) {
204
224
  const problems = [];
225
+ if (!extraRanges || !dangerousMap) {
226
+ const defaults = getBaseDefaults();
227
+ extraRanges ??= defaults.ranges;
228
+ dangerousMap ??= defaults.dangerous;
229
+ }
205
230
  try {
206
231
  const content = readFileSync(filePath, "utf-8");
207
232
  const suffix = extname(filePath).toLowerCase();
@@ -211,15 +236,15 @@ function checkFile(filePath, maxProblems = 5) {
211
236
  for (const char of content) {
212
237
  position++;
213
238
  const code = char.codePointAt(0);
214
- if (isDangerousChar(char)) {
239
+ if (dangerousMap.has(code)) {
215
240
  if (!commentMask.has(offset)) {
216
- const name = DANGEROUS_CODEPOINTS.get(code);
241
+ const name = dangerousMap.get(code);
217
242
  const hex = code.toString(16).toUpperCase().padStart(4, "0");
218
243
  problems.push(
219
244
  `Dangerous character in code at position ${position}: U+${hex} (${name})`
220
245
  );
221
246
  }
222
- } else if (!isAllowedChar(char)) {
247
+ } else if (!isInRanges(code, extraRanges)) {
223
248
  const hex = code.toString(16).toUpperCase().padStart(4, "0");
224
249
  problems.push(
225
250
  `Illegal character at position ${position}: ${JSON.stringify(char)} (U+${hex})`
@@ -230,15 +255,169 @@ function checkFile(filePath, maxProblems = 5) {
230
255
  }
231
256
  offset += char.length;
232
257
  }
233
- } catch {
234
- problems.push(`Failed to read file: ${filePath}`);
258
+ } catch (e) {
259
+ problems.push(`Failed to read file: ${filePath} (${e})`);
235
260
  }
236
261
  return problems;
237
262
  }
238
- function checkPaths(paths) {
263
+ var SKIP_SUFFIXES = /* @__PURE__ */ new Set([
264
+ // Bytecode
265
+ ".pyc",
266
+ ".pyo",
267
+ // Images
268
+ ".png",
269
+ ".jpg",
270
+ ".jpeg",
271
+ ".gif",
272
+ ".bmp",
273
+ ".ico",
274
+ ".svg",
275
+ ".webp",
276
+ // Fonts
277
+ ".ttf",
278
+ ".otf",
279
+ ".woff",
280
+ ".woff2",
281
+ ".eot",
282
+ // Archives
283
+ ".zip",
284
+ ".tar",
285
+ ".gz",
286
+ ".bz2",
287
+ ".xz",
288
+ ".7z",
289
+ // Compiled / binary
290
+ ".so",
291
+ ".dylib",
292
+ ".dll",
293
+ ".exe",
294
+ ".o",
295
+ ".a",
296
+ ".whl",
297
+ ".egg",
298
+ // Media
299
+ ".mp3",
300
+ ".mp4",
301
+ ".wav",
302
+ ".avi",
303
+ ".mov",
304
+ ".flac",
305
+ ".ogg",
306
+ // Documents
307
+ ".pdf",
308
+ ".doc",
309
+ ".docx",
310
+ ".xls",
311
+ ".xlsx",
312
+ ".ppt",
313
+ ".pptx",
314
+ // Data
315
+ ".db",
316
+ ".sqlite",
317
+ ".sqlite3",
318
+ ".pickle",
319
+ ".pkl"
320
+ ]);
321
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
322
+ "__pycache__",
323
+ "node_modules",
324
+ ".git",
325
+ ".venv",
326
+ "venv",
327
+ ".tox",
328
+ ".mypy_cache",
329
+ ".pytest_cache",
330
+ ".ruff_cache",
331
+ "dist",
332
+ "build"
333
+ ]);
334
+ var DEFAULT_DIRS = ["src", "tests", "examples"];
335
+ var DEFAULT_GLOBS = ["*.md", "*.yml", "*.yaml", "*.json", ".gitignore"];
336
+ function walkDir(directory) {
337
+ const files = [];
338
+ let entries;
339
+ try {
340
+ entries = readdirSync(directory).sort();
341
+ } catch {
342
+ return files;
343
+ }
344
+ for (const name of entries) {
345
+ if (name.startsWith(".")) continue;
346
+ const fullPath = join(directory, name);
347
+ let stat;
348
+ try {
349
+ stat = statSync(fullPath);
350
+ } catch {
351
+ continue;
352
+ }
353
+ if (stat.isDirectory()) {
354
+ if (SKIP_DIRS.has(name) || name.endsWith(".egg-info")) continue;
355
+ files.push(...walkDir(fullPath));
356
+ } else if (stat.isFile()) {
357
+ if (SKIP_SUFFIXES.has(extname(name).toLowerCase())) continue;
358
+ files.push(fullPath);
359
+ }
360
+ }
361
+ return files;
362
+ }
363
+ function defaultProjectFiles() {
364
+ const cwd = process.cwd();
365
+ const files = [];
366
+ for (const dirname3 of DEFAULT_DIRS) {
367
+ const d = join(cwd, dirname3);
368
+ if (existsSync(d) && statSync(d).isDirectory()) {
369
+ files.push(...walkDir(d));
370
+ }
371
+ }
372
+ for (const pattern of DEFAULT_GLOBS) {
373
+ if (pattern.startsWith("*.")) {
374
+ const suffix = pattern.slice(1);
375
+ try {
376
+ for (const name of readdirSync(cwd).sort()) {
377
+ if (name.endsWith(suffix) && statSync(join(cwd, name)).isFile()) {
378
+ files.push(join(cwd, name));
379
+ }
380
+ }
381
+ } catch {
382
+ }
383
+ } else {
384
+ const fullPath = join(cwd, pattern);
385
+ if (existsSync(fullPath) && statSync(fullPath).isFile()) {
386
+ files.push(fullPath);
387
+ }
388
+ }
389
+ }
390
+ return files;
391
+ }
392
+ function resolvePaths(paths) {
393
+ if (paths.length === 0) {
394
+ return defaultProjectFiles();
395
+ }
396
+ const result = [];
397
+ for (const p of paths) {
398
+ try {
399
+ if (statSync(p).isDirectory()) {
400
+ result.push(...walkDir(p));
401
+ } else {
402
+ result.push(p);
403
+ }
404
+ } catch {
405
+ result.push(p);
406
+ }
407
+ }
408
+ return result;
409
+ }
410
+ function checkPaths(paths, extraRanges, dangerousMap) {
411
+ const resolved = resolvePaths(paths);
412
+ if (resolved.length === 0) {
413
+ console.log("No files to check.");
414
+ return 0;
415
+ }
239
416
  let hasError = false;
240
- for (const path2 of paths) {
241
- const problems = checkFile(path2);
417
+ let checked = 0;
418
+ for (const path2 of resolved) {
419
+ const problems = checkFile(path2, 5, extraRanges, dangerousMap);
420
+ checked++;
242
421
  if (problems.length > 0) {
243
422
  hasError = true;
244
423
  console.log(`
@@ -248,12 +427,15 @@ ${path2} contains illegal characters:`);
248
427
  }
249
428
  }
250
429
  }
430
+ if (!hasError) {
431
+ console.log(`All ${checked} files passed.`);
432
+ }
251
433
  return hasError ? 1 : 0;
252
434
  }
253
435
 
254
436
  // src/check-imports.ts
255
- import { readFileSync as readFileSync2, readdirSync, statSync } from "fs";
256
- import { join, relative, sep, extname as extname2, basename } from "path";
437
+ import { readFileSync as readFileSync2, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
438
+ import { join as join2, relative, sep as sep2, extname as extname2, basename } from "path";
257
439
  import ts from "typescript";
258
440
  var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([
259
441
  ".ts",
@@ -273,7 +455,7 @@ var INDEX_BASENAMES = /* @__PURE__ */ new Set([
273
455
  ]);
274
456
  function fileToModule(filePath, srcDir) {
275
457
  const rel = relative(srcDir, filePath);
276
- const parts = rel.split(sep);
458
+ const parts = rel.split(sep2);
277
459
  const last = parts[parts.length - 1];
278
460
  if (last === "index.ts" || last === "index.js" || last === "index.tsx" || last === "index.jsx") {
279
461
  parts.pop();
@@ -341,13 +523,13 @@ function resolveImports(rawImports, basePackage, currentModule, isPackage) {
341
523
  function findSourceFiles(dir) {
342
524
  const results = [];
343
525
  function walk(d) {
344
- const entries = readdirSync(d);
526
+ const entries = readdirSync2(d);
345
527
  for (const entry of entries) {
346
528
  if (entry === "node_modules" || entry === "dist" || entry === ".git") {
347
529
  continue;
348
530
  }
349
- const full = join(d, entry);
350
- const stat = statSync(full);
531
+ const full = join2(d, entry);
532
+ const stat = statSync2(full);
351
533
  if (stat.isDirectory()) {
352
534
  walk(full);
353
535
  } else if (SUPPORTED_EXTENSIONS.has(extname2(entry))) {
@@ -435,7 +617,7 @@ function findCycles(graph) {
435
617
  function checkCircularImports(srcDir, basePackage) {
436
618
  let stat;
437
619
  try {
438
- stat = statSync(srcDir);
620
+ stat = statSync2(srcDir);
439
621
  } catch {
440
622
  console.error(`Error: ${srcDir}/ directory not found`);
441
623
  return 1;
@@ -463,10 +645,10 @@ Found ${cycles.length} circular import(s):
463
645
 
464
646
  // src/config.ts
465
647
  import { readFileSync as readFileSync3 } from "fs";
466
- import { join as join2 } from "path";
648
+ import { join as join3 } from "path";
467
649
  function loadConfig(projectDir) {
468
650
  const dir = projectDir ?? process.cwd();
469
- const pkgPath = join2(dir, "package.json");
651
+ const pkgPath = join3(dir, "package.json");
470
652
  let raw;
471
653
  try {
472
654
  raw = readFileSync3(pkgPath, "utf-8");
@@ -483,12 +665,12 @@ function loadConfig(projectDir) {
483
665
 
484
666
  // src/index.ts
485
667
  import { readFileSync as readFileSync4 } from "fs";
486
- import { dirname, join as join3 } from "path";
487
- import { fileURLToPath as fileURLToPath2 } from "url";
668
+ import { dirname as dirname2, join as join4 } from "path";
669
+ import { fileURLToPath as fileURLToPath3 } from "url";
488
670
  function readVersion() {
489
- const thisDir = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath2(import.meta.url));
671
+ const thisDir = typeof __dirname !== "undefined" ? __dirname : dirname2(fileURLToPath3(import.meta.url));
490
672
  try {
491
- const pkg = JSON.parse(readFileSync4(join3(thisDir, "..", "package.json"), "utf-8"));
673
+ const pkg = JSON.parse(readFileSync4(join4(thisDir, "..", "package.json"), "utf-8"));
492
674
  return pkg.version ?? "0.0.0";
493
675
  } catch {
494
676
  return "0.0.0";
@@ -503,6 +685,10 @@ export {
503
685
  fileToModule,
504
686
  findCycles,
505
687
  isAllowedChar,
688
+ isDangerousChar,
689
+ loadCharset,
506
690
  loadConfig,
691
+ resolveCharsets,
692
+ resolvePaths,
507
693
  version
508
694
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "apdev-js",
3
- "version": "0.1.1",
4
- "description": "Shared development tools for TypeScript/JavaScript projects - character validation, circular import detection, and more",
3
+ "version": "0.2.1",
4
+ "description": "General-purpose development tools for TypeScript/JavaScript projects - character validation, circular import detection, and more",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.js",
@@ -18,7 +18,8 @@
18
18
  },
19
19
  "files": [
20
20
  "dist",
21
- "release.sh"
21
+ "release.sh",
22
+ "src/charsets"
22
23
  ],
23
24
  "scripts": {
24
25
  "build": "tsup",
@@ -30,9 +31,16 @@
30
31
  },
31
32
  "keywords": [
32
33
  "development-tools",
34
+ "quality-assurance",
35
+ "linting",
36
+ "static-analysis",
33
37
  "character-validation",
34
38
  "circular-imports",
35
- "linting"
39
+ "release-automation",
40
+ "unicode-checker",
41
+ "trojan-source-detection",
42
+ "typescript",
43
+ "javascript"
36
44
  ],
37
45
  "author": "aipartnerup <tercel.yi@gmail.com>",
38
46
  "license": "Apache-2.0",
@@ -46,12 +54,20 @@
46
54
  "url": "https://github.com/aipartnerup/apdev/issues"
47
55
  },
48
56
  "dependencies": {
49
- "commander": "^13.0.0",
50
- "typescript": "^5.7.0"
57
+ "commander": "^13.0.0"
58
+ },
59
+ "peerDependencies": {
60
+ "typescript": "^5.0.0"
61
+ },
62
+ "peerDependenciesMeta": {
63
+ "typescript": {
64
+ "optional": true
65
+ }
51
66
  },
52
67
  "devDependencies": {
53
68
  "@types/node": "^22.0.0",
54
69
  "tsup": "^8.0.0",
70
+ "typescript": "^5.7.0",
55
71
  "vitest": "^3.0.0"
56
72
  }
57
- }
73
+ }
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "base",
3
+ "description": "Default: ASCII + emoji + technical symbols",
4
+ "emoji_ranges": [
5
+ { "start": "0x1F300", "end": "0x1F5FF", "name": "Symbols and Pictographs" },
6
+ { "start": "0x1F600", "end": "0x1F64F", "name": "Emoticons" },
7
+ { "start": "0x1F680", "end": "0x1F6FF", "name": "Transport and Map Symbols" },
8
+ { "start": "0x1F780", "end": "0x1F7FF", "name": "Geometric Shapes Extended" },
9
+ { "start": "0x1F900", "end": "0x1F9FF", "name": "Supplemental Symbols and Pictographs" },
10
+ { "start": "0x2600", "end": "0x26FF", "name": "Miscellaneous Symbols" },
11
+ { "start": "0x2700", "end": "0x27BF", "name": "Dingbats" }
12
+ ],
13
+ "extra_ranges": [
14
+ { "start": "0x0080", "end": "0x00FF", "name": "Latin-1 Supplement" },
15
+ { "start": "0x2000", "end": "0x206F", "name": "General Punctuation" },
16
+ { "start": "0x2100", "end": "0x214F", "name": "Letterlike Symbols" },
17
+ { "start": "0x2190", "end": "0x21FF", "name": "Arrows" },
18
+ { "start": "0x2200", "end": "0x22FF", "name": "Mathematical Operators" },
19
+ { "start": "0x2300", "end": "0x23FF", "name": "Miscellaneous Technical" },
20
+ { "start": "0x2500", "end": "0x257F", "name": "Box Drawing" },
21
+ { "start": "0x2580", "end": "0x259F", "name": "Block Elements" },
22
+ { "start": "0x25A0", "end": "0x25FF", "name": "Geometric Shapes" },
23
+ { "start": "0x2800", "end": "0x28FF", "name": "Braille Patterns" },
24
+ { "start": "0x2B00", "end": "0x2BFF", "name": "Miscellaneous Symbols and Arrows" },
25
+ { "start": "0xFE00", "end": "0xFE0F", "name": "Variation Selectors" }
26
+ ],
27
+ "dangerous": [
28
+ { "code": "0x202A", "name": "LEFT-TO-RIGHT EMBEDDING" },
29
+ { "code": "0x202B", "name": "RIGHT-TO-LEFT EMBEDDING" },
30
+ { "code": "0x202C", "name": "POP DIRECTIONAL FORMATTING" },
31
+ { "code": "0x202D", "name": "LEFT-TO-RIGHT OVERRIDE" },
32
+ { "code": "0x202E", "name": "RIGHT-TO-LEFT OVERRIDE" },
33
+ { "code": "0x2066", "name": "LEFT-TO-RIGHT ISOLATE" },
34
+ { "code": "0x2067", "name": "RIGHT-TO-LEFT ISOLATE" },
35
+ { "code": "0x2068", "name": "FIRST STRONG ISOLATE" },
36
+ { "code": "0x2069", "name": "POP DIRECTIONAL ISOLATE" },
37
+ { "code": "0x200B", "name": "ZERO WIDTH SPACE" },
38
+ { "code": "0x200C", "name": "ZERO WIDTH NON-JOINER" },
39
+ { "code": "0x200D", "name": "ZERO WIDTH JOINER" },
40
+ { "code": "0x200E", "name": "LEFT-TO-RIGHT MARK" },
41
+ { "code": "0x200F", "name": "RIGHT-TO-LEFT MARK" },
42
+ { "code": "0x2060", "name": "WORD JOINER" }
43
+ ]
44
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "chinese",
3
+ "description": "Chinese: CJK Unified Ideographs, CJK Punctuation, Fullwidth Forms",
4
+ "extra_ranges": [
5
+ { "start": "0x3000", "end": "0x303F", "name": "CJK Symbols and Punctuation" },
6
+ { "start": "0x4E00", "end": "0x9FFF", "name": "CJK Unified Ideographs" },
7
+ { "start": "0xFF00", "end": "0xFFEF", "name": "Fullwidth Forms" }
8
+ ]
9
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "japanese",
3
+ "description": "Japanese: Hiragana, Katakana, CJK Unified Ideographs, CJK Punctuation, Fullwidth Forms",
4
+ "extra_ranges": [
5
+ { "start": "0x3000", "end": "0x303F", "name": "CJK Symbols and Punctuation" },
6
+ { "start": "0x3040", "end": "0x309F", "name": "Hiragana" },
7
+ { "start": "0x30A0", "end": "0x30FF", "name": "Katakana" },
8
+ { "start": "0x4E00", "end": "0x9FFF", "name": "CJK Unified Ideographs" },
9
+ { "start": "0xFF00", "end": "0xFFEF", "name": "Fullwidth Forms" }
10
+ ]
11
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "korean",
3
+ "description": "Korean: Hangul Jamo, Hangul Syllables, CJK Unified Ideographs, CJK Punctuation, Fullwidth Forms",
4
+ "extra_ranges": [
5
+ { "start": "0x1100", "end": "0x11FF", "name": "Hangul Jamo" },
6
+ { "start": "0x3000", "end": "0x303F", "name": "CJK Symbols and Punctuation" },
7
+ { "start": "0x4E00", "end": "0x9FFF", "name": "CJK Unified Ideographs" },
8
+ { "start": "0xAC00", "end": "0xD7AF", "name": "Hangul Syllables" },
9
+ { "start": "0xFF00", "end": "0xFFEF", "name": "Fullwidth Forms" }
10
+ ]
11
+ }