@diia-inhouse/diia-logger 4.6.0 → 4.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/dist/config.js CHANGED
@@ -66,6 +66,7 @@ const defaultOptions = {
66
66
  "firstNameUA",
67
67
  "lastNameUA",
68
68
  "middleNameUA",
69
+ "fullNameUA",
69
70
  "serialNumber",
70
71
  "department",
71
72
  "departmentUA",
@@ -75,6 +76,8 @@ const defaultOptions = {
75
76
  "recordNumber",
76
77
  "firstNameEN",
77
78
  "lastNameEN",
79
+ "middleNameEN",
80
+ "fullNameEN",
78
81
  "docNumber",
79
82
  "documentRegistrationPlaceUA",
80
83
  "currentRegistrationPlaceUA",
@@ -1,57 +1,79 @@
1
1
  //#region src/redactors/fullName.ts
2
- function checkIsLowercaseLetter(char) {
3
- const code = char?.codePointAt(0);
4
- if (code === void 0) return false;
5
- if (code >= 97 && code <= 122) return true;
6
- return code >= 1072 && code <= 1103 || code === 1108 || code === 1110 || code === 1111 || code === 1169;
2
+ const minParts = 2;
3
+ const maxParts = 6;
4
+ const nameSeparators = new Set([
5
+ "'",
6
+ "’",
7
+ "-"
8
+ ]);
9
+ function isCasedLetter(char) {
10
+ return char.toLowerCase() !== char.toUpperCase();
11
+ }
12
+ function isUpperCaseLetter(char) {
13
+ return isCasedLetter(char) && char === char.toUpperCase();
14
+ }
15
+ function isLowerCaseLetter(char) {
16
+ return isCasedLetter(char) && char === char.toLowerCase();
17
+ }
18
+ function isNameChar(char) {
19
+ return isCasedLetter(char) || nameSeparators.has(char);
20
+ }
21
+ function splitSegments(word) {
22
+ const segments = [];
23
+ let current = "";
24
+ for (const char of word) if (nameSeparators.has(char)) {
25
+ segments.push(current);
26
+ current = "";
27
+ } else current += char;
28
+ segments.push(current);
29
+ return segments;
30
+ }
31
+ function isTitleCaseSegment(segment) {
32
+ if (!isUpperCaseLetter(segment[0])) return false;
33
+ for (let i = 1; i < segment.length; i++) if (!isLowerCaseLetter(segment[i])) return false;
34
+ return true;
35
+ }
36
+ function isUpperCaseSegment(segment) {
37
+ for (const char of segment) if (!isUpperCaseLetter(char)) return false;
38
+ return true;
7
39
  }
8
40
  function findNextWordBoundary(text, startIndex) {
9
- for (let i = startIndex; i < text.length; i++) if (text[i] === " ") return i;
41
+ for (let i = startIndex; i < text.length; i++) if (!isNameChar(text[i])) return i;
10
42
  return text.length;
11
43
  }
12
- function isUpperCase(char) {
13
- return checkIsLowercaseLetter(char?.toLowerCase()) && char === char?.toUpperCase();
14
- }
15
44
  function isNamePart(word) {
16
- if (!isUpperCase(word?.[0])) return false;
17
- let prevChar;
18
- for (let i = 1; i < (word?.length || 0); i++) {
19
- const char = word?.[i];
20
- if (checkIsLowercaseLetter(char) || char === "-" || prevChar === "-" && checkIsLowercaseLetter(char?.toLowerCase())) return true;
21
- prevChar = char;
22
- }
23
- return false;
45
+ if (word.length < 2) return false;
46
+ const segments = splitSegments(word);
47
+ if (segments.some((segment) => segment === "")) return false;
48
+ return segments.every((segment) => isTitleCaseSegment(segment)) || segments.every((segment) => isUpperCaseSegment(segment));
24
49
  }
25
50
  function extractFullName(text, startIndex) {
26
51
  const parts = [];
27
52
  let currentIndex = startIndex;
28
- while (currentIndex < text.length) {
53
+ let endIndex = startIndex;
54
+ while (currentIndex < text.length && parts.length < maxParts) {
29
55
  const wordEnd = findNextWordBoundary(text, currentIndex);
30
56
  const word = text.slice(currentIndex, wordEnd);
31
57
  if (!isNamePart(word)) break;
32
58
  parts.push(word);
33
- currentIndex = wordEnd;
34
- if (currentIndex < text.length && text[currentIndex] === " ") {
35
- if (parts.length >= 6) break;
36
- currentIndex++;
37
- } else break;
59
+ endIndex = wordEnd;
60
+ if (text[wordEnd] !== " ") break;
61
+ currentIndex = wordEnd + 1;
38
62
  }
39
- if (parts.length >= 2 && parts.length <= 6) return [parts.join(" "), currentIndex];
63
+ if (parts.length >= minParts) return [parts.join(" "), endIndex];
40
64
  return [void 0, startIndex];
41
65
  }
42
66
  function nameToInitials(fullName) {
43
- return fullName.split(/\s/).map((part) => `${part.split("-")[0][0]}.`).join("");
67
+ return fullName.split(" ").map((part) => `${splitSegments(part)[0][0]}.`).join("");
44
68
  }
45
69
  function redactFullName(text) {
46
70
  let result = "";
47
71
  let currentIndex = 0;
48
72
  while (currentIndex < text.length) {
49
- const currentChar = text[currentIndex];
50
- if (isUpperCase(currentChar)) {
73
+ if (isUpperCaseLetter(text[currentIndex])) {
51
74
  const [fullName, endIndex] = extractFullName(text, currentIndex);
52
75
  if (fullName) {
53
- const initials = nameToInitials(fullName);
54
- result += `[Fullname redacted: ${initials}] `;
76
+ result += `[Fullname redacted: ${nameToInitials(fullName)}]`;
55
77
  currentIndex = endIndex;
56
78
  continue;
57
79
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diia-inhouse/diia-logger",
3
- "version": "4.6.0",
3
+ "version": "4.7.1",
4
4
  "type": "module",
5
5
  "description": "Logger package",
6
6
  "main": "dist/index.js",
@@ -43,9 +43,9 @@
43
43
  "devDependencies": {
44
44
  "@diia-inhouse/configs": "7.2.2",
45
45
  "@diia-inhouse/eslint-plugin": "1.9.13",
46
- "@diia-inhouse/oxc-config": "3.0.4",
47
- "@diia-inhouse/types": "14.2.15",
48
- "@diia-inhouse/utils": "6.1.15",
46
+ "@diia-inhouse/oxc-config": "3.1.0",
47
+ "@diia-inhouse/types": "14.3.0",
48
+ "@diia-inhouse/utils": "6.1.16",
49
49
  "@types/lodash": "4.17.24",
50
50
  "@vitest/coverage-v8": "4.1.11",
51
51
  "@vitest/ui": "4.1.11",
package/src/config.ts CHANGED
@@ -65,6 +65,7 @@ export const defaultOptions: LoggerOptions = {
65
65
  'firstNameUA',
66
66
  'lastNameUA',
67
67
  'middleNameUA',
68
+ 'fullNameUA',
68
69
  'serialNumber',
69
70
  'department',
70
71
  'departmentUA',
@@ -74,6 +75,8 @@ export const defaultOptions: LoggerOptions = {
74
75
  'recordNumber',
75
76
  'firstNameEN',
76
77
  'lastNameEN',
78
+ 'middleNameEN',
79
+ 'fullNameEN',
77
80
  'docNumber',
78
81
  'documentRegistrationPlaceUA',
79
82
  'currentRegistrationPlaceUA',
@@ -1,28 +1,69 @@
1
- function checkIsLowercaseLetter(char: string | undefined): boolean {
2
- const code = char?.codePointAt(0)
3
- if (code === undefined) {
1
+ const minParts = 2
2
+ const maxParts = 6
3
+
4
+ const nameSeparators = new Set(["'", '’', '-'])
5
+
6
+ function isCasedLetter(char: string): boolean {
7
+ return char.toLowerCase() !== char.toUpperCase()
8
+ }
9
+
10
+ function isUpperCaseLetter(char: string): boolean {
11
+ return isCasedLetter(char) && char === char.toUpperCase()
12
+ }
13
+
14
+ function isLowerCaseLetter(char: string): boolean {
15
+ return isCasedLetter(char) && char === char.toLowerCase()
16
+ }
17
+
18
+ function isNameChar(char: string): boolean {
19
+ return isCasedLetter(char) || nameSeparators.has(char)
20
+ }
21
+
22
+ function splitSegments(word: string): string[] {
23
+ const segments: string[] = []
24
+ let current = ''
25
+
26
+ for (const char of word) {
27
+ if (nameSeparators.has(char)) {
28
+ segments.push(current)
29
+ current = ''
30
+ } else {
31
+ current += char
32
+ }
33
+ }
34
+
35
+ segments.push(current)
36
+
37
+ return segments
38
+ }
39
+
40
+ function isTitleCaseSegment(segment: string): boolean {
41
+ if (!isUpperCaseLetter(segment[0])) {
4
42
  return false
5
43
  }
6
44
 
7
- const isLatin = code >= 97 && code <= 122 // a-z
8
- if (isLatin) {
9
- return true
45
+ for (let i = 1; i < segment.length; i++) {
46
+ if (!isLowerCaseLetter(segment[i])) {
47
+ return false
48
+ }
10
49
  }
11
50
 
12
- const isUkrainian =
13
- (code >= 0x04_30 && code <= 0x04_4f) || // а-я
14
- code === 0x04_54 || // є
15
- code === 0x04_56 || // і
16
- code === 0x04_57 || // ї
17
- code === 0x04_91 // ґ
51
+ return true
52
+ }
53
+
54
+ function isUpperCaseSegment(segment: string): boolean {
55
+ for (const char of segment) {
56
+ if (!isUpperCaseLetter(char)) {
57
+ return false
58
+ }
59
+ }
18
60
 
19
- return isUkrainian
61
+ return true
20
62
  }
21
63
 
22
64
  function findNextWordBoundary(text: string, startIndex: number): number {
23
65
  for (let i = startIndex; i < text.length; i++) {
24
- const char = text[i]
25
- if (char === ' ') {
66
+ if (!isNameChar(text[i])) {
26
67
  return i
27
68
  }
28
69
  }
@@ -30,35 +71,25 @@ function findNextWordBoundary(text: string, startIndex: number): number {
30
71
  return text.length
31
72
  }
32
73
 
33
- function isUpperCase(char: string | undefined): boolean {
34
- const isLetter = checkIsLowercaseLetter(char?.toLowerCase())
35
-
36
- return isLetter && char === char?.toUpperCase()
37
- }
38
-
39
- function isNamePart(word: string | undefined): boolean {
40
- if (!isUpperCase(word?.[0])) {
74
+ function isNamePart(word: string): boolean {
75
+ if (word.length < 2) {
41
76
  return false
42
77
  }
43
78
 
44
- let prevChar
45
- for (let i = 1; i < (word?.length || 0); i++) {
46
- const char = word?.[i]
47
- if (checkIsLowercaseLetter(char) || char === '-' || (prevChar === '-' && checkIsLowercaseLetter(char?.toLowerCase()))) {
48
- return true
49
- }
50
-
51
- prevChar = char
79
+ const segments = splitSegments(word)
80
+ if (segments.some((segment) => segment === '')) {
81
+ return false
52
82
  }
53
83
 
54
- return false
84
+ return segments.every((segment) => isTitleCaseSegment(segment)) || segments.every((segment) => isUpperCaseSegment(segment))
55
85
  }
56
86
 
57
87
  function extractFullName(text: string, startIndex: number): [string | undefined, number] {
58
88
  const parts: string[] = []
59
89
  let currentIndex = startIndex
90
+ let endIndex = startIndex
60
91
 
61
- while (currentIndex < text.length) {
92
+ while (currentIndex < text.length && parts.length < maxParts) {
62
93
  const wordEnd = findNextWordBoundary(text, currentIndex)
63
94
  const word = text.slice(currentIndex, wordEnd)
64
95
 
@@ -67,22 +98,17 @@ function extractFullName(text: string, startIndex: number): [string | undefined,
67
98
  }
68
99
 
69
100
  parts.push(word)
101
+ endIndex = wordEnd
70
102
 
71
- currentIndex = wordEnd
72
-
73
- if (currentIndex < text.length && text[currentIndex] === ' ') {
74
- if (parts.length >= 6) {
75
- break
76
- }
77
-
78
- currentIndex++
79
- } else {
103
+ if (text[wordEnd] !== ' ') {
80
104
  break
81
105
  }
106
+
107
+ currentIndex = wordEnd + 1
82
108
  }
83
109
 
84
- if (parts.length >= 2 && parts.length <= 6) {
85
- return [parts.join(' '), currentIndex]
110
+ if (parts.length >= minParts) {
111
+ return [parts.join(' '), endIndex]
86
112
  }
87
113
 
88
114
  return [undefined, startIndex]
@@ -90,8 +116,8 @@ function extractFullName(text: string, startIndex: number): [string | undefined,
90
116
 
91
117
  function nameToInitials(fullName: string): string {
92
118
  return fullName
93
- .split(/\s/)
94
- .map((part) => `${part.split('-')[0][0]}.`)
119
+ .split(' ')
120
+ .map((part) => `${splitSegments(part)[0][0]}.`)
95
121
  .join('')
96
122
  }
97
123
 
@@ -100,14 +126,11 @@ export function redactFullName(text: string): string {
100
126
  let currentIndex = 0
101
127
 
102
128
  while (currentIndex < text.length) {
103
- const currentChar = text[currentIndex]
104
- if (isUpperCase(currentChar)) {
129
+ if (isUpperCaseLetter(text[currentIndex])) {
105
130
  const [fullName, endIndex] = extractFullName(text, currentIndex)
106
131
 
107
132
  if (fullName) {
108
- const initials = nameToInitials(fullName)
109
-
110
- result += `[Fullname redacted: ${initials}] `
133
+ result += `[Fullname redacted: ${nameToInitials(fullName)}]`
111
134
  currentIndex = endIndex
112
135
  continue
113
136
  }