apphx 2.0.1 → 2.1.0
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/README.md +10 -8
- package/dist/cli.js +41 -11
- package/dist/cli.js.map +1 -1
- package/dist/config.js +4 -2
- package/dist/config.js.map +1 -1
- package/dist/integrations.js +154 -95
- package/dist/integrations.js.map +1 -1
- package/dist/notify.js +267 -0
- package/dist/notify.js.map +1 -0
- package/dist/runner.js +119 -20
- package/dist/runner.js.map +1 -1
- package/dist/schemas.js +57 -2
- package/dist/schemas.js.map +1 -1
- package/dist/vendor/obfuscator/dist/config.js +119 -6
- package/dist/vendor/obfuscator/dist/config.js.map +1 -1
- package/dist/vendor/obfuscator/dist/dfa.js +528 -0
- package/dist/vendor/obfuscator/dist/dfa.js.map +1 -0
- package/dist/vendor/obfuscator/dist/flutter.js +3266 -0
- package/dist/vendor/obfuscator/dist/flutter.js.map +1 -0
- package/dist/vendor/obfuscator/dist/image-utils.js +335 -0
- package/dist/vendor/obfuscator/dist/image-utils.js.map +1 -0
- package/dist/vendor/obfuscator/dist/junk.js +1501 -0
- package/dist/vendor/obfuscator/dist/junk.js.map +1 -0
- package/dist/vendor/obfuscator/dist/lexicon-data.js +972 -0
- package/dist/vendor/obfuscator/dist/lexicon-data.js.map +1 -0
- package/dist/vendor/obfuscator/dist/obfuscate.js +51 -2
- package/dist/vendor/obfuscator/dist/obfuscate.js.map +1 -1
- package/dist/vendor/obfuscator/dist/objc-symbols.js +1819 -0
- package/dist/vendor/obfuscator/dist/objc-symbols.js.map +1 -0
- package/dist/vendor/obfuscator/dist/project-identity.js +852 -0
- package/dist/vendor/obfuscator/dist/project-identity.js.map +1 -0
- package/dist/vendor/obfuscator/dist/string-encrypt.js +330 -0
- package/dist/vendor/obfuscator/dist/string-encrypt.js.map +1 -0
- package/examples/android-java.factory.json +40 -5
- package/examples/expo.factory.json +68 -5
- package/examples/flutter.factory.json +40 -4
- package/examples/luggage.json +1 -0
- package/examples/multi-role-agents.md +129 -0
- package/examples/mygo.factory.json +54 -4
- package/examples/queue-loop-demo.md +146 -0
- package/examples/react-native.factory.json +58 -5
- package/examples/roles.example.json +31 -0
- package/examples/swift.factory.json +60 -5
- package/examples/uni-app.factory.json +58 -5
- package/package.json +1 -1
- package/skills/apphx/SKILL.md +39 -4
- package/skills/apphx/resources/requirement.json +1 -0
- package/skills/apphx-obfuscator/LICENSE +21 -0
- package/skills/apphx-obfuscator/SKILL.md +72 -0
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
import { deriveToken } from "./common.js";
|
|
2
|
+
import { filterList, wordFilterList } from "./lexicon-data.js";
|
|
3
|
+
// src/dfa.ts
|
|
4
|
+
/**
|
|
5
|
+
* Deterministic DFA sensitive-word filter and the algorithms that ship next
|
|
6
|
+
* to it: constant-keyword mixing, project language detection, and ObjC comment
|
|
7
|
+
* removal. GUI, networking and authorization code is intentionally dropped.
|
|
8
|
+
*
|
|
9
|
+
* Everything here is pure and deterministic; keyword names are derived with the
|
|
10
|
+
* seeded helpers from ./common.js, never with Math.random or Date.now.
|
|
11
|
+
*/
|
|
12
|
+
/** Word-termination marker stored inside trie nodes. */
|
|
13
|
+
const DELIMIT = "\0";
|
|
14
|
+
/**
|
|
15
|
+
* Standard DFA (dictionary-tree) sensitive-word filter.
|
|
16
|
+
*
|
|
17
|
+
* Word lists are lower-cased before insertion, matching the original
|
|
18
|
+
* `addSensitiveWords`; messages are scanned as-is. `filter` replaces each hit
|
|
19
|
+
* with `repl` repeated to the length of the matched word and leaves unmatched
|
|
20
|
+
* text unchanged.
|
|
21
|
+
*/
|
|
22
|
+
var DFAFilter = class {
|
|
23
|
+
keywordChains = {};
|
|
24
|
+
delimit = DELIMIT;
|
|
25
|
+
constructor(wordList) {
|
|
26
|
+
this.parseSensitiveWords(wordList);
|
|
27
|
+
}
|
|
28
|
+
parseSensitiveWords(wordList) {
|
|
29
|
+
const words = wordList ?? wordFilterList;
|
|
30
|
+
for (const word of words) this.addSensitiveWords(String(word).trim());
|
|
31
|
+
}
|
|
32
|
+
/** Insert one sensitive word into the trie (lower-cased and trimmed). */
|
|
33
|
+
addSensitiveWords(keyword) {
|
|
34
|
+
const chars = keyword.toLowerCase().trim();
|
|
35
|
+
if (!chars) return;
|
|
36
|
+
let level = this.keywordChains;
|
|
37
|
+
for (const char of chars) {
|
|
38
|
+
let child = level[char];
|
|
39
|
+
if (typeof child !== "object") {
|
|
40
|
+
child = {};
|
|
41
|
+
level[char] = child;
|
|
42
|
+
}
|
|
43
|
+
level = child;
|
|
44
|
+
}
|
|
45
|
+
level[this.delimit] = 0;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Replace every sensitive word with `repl` repeated to the matched word's
|
|
49
|
+
* length. Unmatched text is preserved verbatim.
|
|
50
|
+
*/
|
|
51
|
+
filter(message, repl = "*") {
|
|
52
|
+
const out = [];
|
|
53
|
+
let start = 0;
|
|
54
|
+
while (start < message.length) {
|
|
55
|
+
let level = this.keywordChains;
|
|
56
|
+
let cursor = start;
|
|
57
|
+
let matchEnd = -1;
|
|
58
|
+
while (cursor < message.length) {
|
|
59
|
+
const child = level[message[cursor]];
|
|
60
|
+
if (typeof child !== "object") break;
|
|
61
|
+
level = child;
|
|
62
|
+
cursor += 1;
|
|
63
|
+
if (this.delimit in level) matchEnd = cursor;
|
|
64
|
+
}
|
|
65
|
+
if (matchEnd >= 0) {
|
|
66
|
+
out.push(repl.repeat(matchEnd - start));
|
|
67
|
+
start = matchEnd;
|
|
68
|
+
} else {
|
|
69
|
+
out.push(message[start]);
|
|
70
|
+
start += 1;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return out.join("");
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
const MIN_KEYWORD_LENGTH = 7;
|
|
77
|
+
/** ASCII punctuation without `_` (matches `string.punctuation` minus `_`). */
|
|
78
|
+
const SPECIAL_PUNCTUATION_CHARS = "!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~";
|
|
79
|
+
function countOccurrences(value, char) {
|
|
80
|
+
let count = 0;
|
|
81
|
+
for (const current of value) if (current === char) count += 1;
|
|
82
|
+
return count;
|
|
83
|
+
}
|
|
84
|
+
/** True when `value` contains any ASCII punctuation other than `_`. */
|
|
85
|
+
function hasSpecialString(value) {
|
|
86
|
+
for (const char of value) if (SPECIAL_PUNCTUATION_CHARS.includes(char)) return true;
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
const KEYWORD_IGNORE_LIST = [
|
|
90
|
+
"instance",
|
|
91
|
+
"onceToken",
|
|
92
|
+
"types",
|
|
93
|
+
"WEAKBLOCK",
|
|
94
|
+
"ClassMethod",
|
|
95
|
+
"manager",
|
|
96
|
+
"diagonal",
|
|
97
|
+
"deltas",
|
|
98
|
+
"result",
|
|
99
|
+
"value",
|
|
100
|
+
"className",
|
|
101
|
+
"multiplier",
|
|
102
|
+
"decibel",
|
|
103
|
+
"platform",
|
|
104
|
+
"filePath",
|
|
105
|
+
"source",
|
|
106
|
+
"identifier",
|
|
107
|
+
"components",
|
|
108
|
+
"conversion",
|
|
109
|
+
"calendar",
|
|
110
|
+
"dateFormatter",
|
|
111
|
+
"operationKey",
|
|
112
|
+
"componentFlags",
|
|
113
|
+
"nullable",
|
|
114
|
+
"nonnull",
|
|
115
|
+
"gregorian",
|
|
116
|
+
"processPool",
|
|
117
|
+
"stringValue",
|
|
118
|
+
"encoding",
|
|
119
|
+
"reuseIdentifier",
|
|
120
|
+
"locations",
|
|
121
|
+
"RotQuatG",
|
|
122
|
+
"iterator",
|
|
123
|
+
"gQuatRot",
|
|
124
|
+
"HAVE_AES",
|
|
125
|
+
"colorSpace",
|
|
126
|
+
"contentOffset",
|
|
127
|
+
"aapssView"
|
|
128
|
+
];
|
|
129
|
+
/**
|
|
130
|
+
* Port of `judge_keyword_none`: returns `null` when a candidate keyword must be
|
|
131
|
+
* skipped (too short, system/third-party prefix, common property/method name,
|
|
132
|
+
* or listed in the filter list), otherwise the keyword itself.
|
|
133
|
+
*/
|
|
134
|
+
function judgeKeywordNone(keyword) {
|
|
135
|
+
if (keyword.length <= MIN_KEYWORD_LENGTH) return null;
|
|
136
|
+
if (keyword.includes("ScreenWidth") || keyword.includes("SCREEN_WIDTH")) return null;
|
|
137
|
+
if (keyword.includes("ScreenHeight") || keyword.includes("SCREEN_HEIGHT")) return null;
|
|
138
|
+
if (keyword.startsWith("once")) return null;
|
|
139
|
+
if (keyword.startsWith("CG")) return null;
|
|
140
|
+
if (keyword.startsWith("NS")) return null;
|
|
141
|
+
if (keyword.startsWith("UI")) return null;
|
|
142
|
+
if (keyword.startsWith("UTF")) return null;
|
|
143
|
+
if (keyword.startsWith("BN_")) return null;
|
|
144
|
+
if (keyword.startsWith("SDWebImage")) return null;
|
|
145
|
+
if (keyword.startsWith("PLATFORM_")) return null;
|
|
146
|
+
if (keyword.startsWith("print")) return null;
|
|
147
|
+
if (keyword.startsWith("UNITY") || keyword.startsWith("Unity")) return null;
|
|
148
|
+
if (keyword.startsWith("APP_CONTROLLER")) return null;
|
|
149
|
+
if (keyword.startsWith("s_")) return null;
|
|
150
|
+
if (keyword.startsWith("count")) return null;
|
|
151
|
+
if (keyword.startsWith("_")) return null;
|
|
152
|
+
if (keyword.startsWith("shared")) return null;
|
|
153
|
+
if (keyword.startsWith("sd_")) return null;
|
|
154
|
+
if (keyword.startsWith("SDImage")) return null;
|
|
155
|
+
if (keyword.startsWith("metamacro_")) return null;
|
|
156
|
+
if (keyword.startsWith("kCGImage")) return null;
|
|
157
|
+
if (keyword.startsWith("YYCGImage")) return null;
|
|
158
|
+
if (keyword.startsWith("MJRefresh")) return null;
|
|
159
|
+
if (keyword.includes("_shared")) return null;
|
|
160
|
+
if (keyword.includes("Base64Encode")) return null;
|
|
161
|
+
if (keyword.includes("md5Key")) return null;
|
|
162
|
+
if (keyword.includes("Instance") || keyword.includes("instance")) return null;
|
|
163
|
+
if (keyword.includes("Assert") || keyword.includes("assert")) return null;
|
|
164
|
+
if (keyword.startsWith("block")) return null;
|
|
165
|
+
if (keyword.startsWith("and")) return null;
|
|
166
|
+
if (keyword.startsWith("NSLog")) return null;
|
|
167
|
+
if (keyword.startsWith("GLES_")) return null;
|
|
168
|
+
if (keyword.startsWith("GTM_")) return null;
|
|
169
|
+
if (keyword.startsWith("CF_")) return null;
|
|
170
|
+
if (keyword.startsWith("SSL3_")) return null;
|
|
171
|
+
if (keyword.startsWith("TLS1_")) return null;
|
|
172
|
+
if (keyword.startsWith("kCVPixel")) return null;
|
|
173
|
+
if (keyword.startsWith("set") || keyword.startsWith("Set")) return null;
|
|
174
|
+
if (keyword.startsWith("get") || keyword.startsWith("Get")) return null;
|
|
175
|
+
if (keyword.startsWith("public_") || keyword.startsWith("PUBLIC_")) return null;
|
|
176
|
+
if (keyword.startsWith("DETAIL_") || keyword.startsWith("Detail_")) return null;
|
|
177
|
+
if (keyword.startsWith("Exchange")) return null;
|
|
178
|
+
if (keyword.startsWith("Compare")) return null;
|
|
179
|
+
if (keyword.startsWith("Increment")) return null;
|
|
180
|
+
if (keyword.startsWith("Decrement")) return null;
|
|
181
|
+
if (keyword.startsWith("IL2CPP_") || keyword.startsWith("il2cpp_")) return null;
|
|
182
|
+
if (keyword.startsWith("detail_")) return null;
|
|
183
|
+
if (keyword.startsWith("Baselib_")) return null;
|
|
184
|
+
if (keyword.startsWith("DEBUG") || keyword.endsWith("DEBUG")) return null;
|
|
185
|
+
if (keyword.startsWith("init")) return null;
|
|
186
|
+
if (keyword.startsWith("Log") || keyword.startsWith("log") || keyword.startsWith("LOG")) return null;
|
|
187
|
+
if (keyword.endsWith("log") || keyword.endsWith("Log") || keyword.endsWith("LOG")) return null;
|
|
188
|
+
if (keyword.includes("Log")) return null;
|
|
189
|
+
if (keyword.includes("_get_")) return null;
|
|
190
|
+
if (keyword.includes("_set_")) return null;
|
|
191
|
+
if (keyword.includes("Class") || keyword.includes("class")) return null;
|
|
192
|
+
if (keyword.endsWith("_SIZE") || keyword.endsWith("_size") || keyword.endsWith("Size")) return null;
|
|
193
|
+
if (keyword.endsWith("DEBUG") || keyword.endsWith("Debug")) return null;
|
|
194
|
+
if (keyword.endsWith("_var")) return null;
|
|
195
|
+
if (keyword.endsWith("Internal")) return null;
|
|
196
|
+
if (keyword.startsWith("Internal")) return null;
|
|
197
|
+
if (keyword.startsWith("RCKit")) return null;
|
|
198
|
+
if (keyword.startsWith("MD5_")) return null;
|
|
199
|
+
if (keyword.startsWith("EVP_")) return null;
|
|
200
|
+
if (keyword.startsWith("NID_")) return null;
|
|
201
|
+
if (keyword.startsWith("VERSION") || keyword.startsWith("Version")) return null;
|
|
202
|
+
if (keyword.startsWith("METHOD_")) return null;
|
|
203
|
+
if (keyword.startsWith("COMPILER_")) return null;
|
|
204
|
+
if (keyword.startsWith("BASELIB") || keyword.startsWith("Baselib")) return null;
|
|
205
|
+
if (KEYWORD_IGNORE_LIST.includes(keyword)) return null;
|
|
206
|
+
if (keyword.includes("_")) {
|
|
207
|
+
const parts = keyword.split("_");
|
|
208
|
+
if (parts[parts.length - 1].length === 1) return null;
|
|
209
|
+
if (parts[0].length === 1) return null;
|
|
210
|
+
}
|
|
211
|
+
if (keyword.includes("String") && keyword.replaceAll("String", "").length <= 3) return null;
|
|
212
|
+
if (keyword.includes("string") && keyword.replaceAll("string", "").length <= 3) return null;
|
|
213
|
+
if (hasSpecialString(keyword)) return null;
|
|
214
|
+
if (filterList.includes(keyword)) return null;
|
|
215
|
+
return keyword;
|
|
216
|
+
}
|
|
217
|
+
/** Split a source line the way `is_contain_const_keyword` does before keyword selection. */
|
|
218
|
+
function splitKeywordLine(line, splitOnParen) {
|
|
219
|
+
let value = line.split("//")[0];
|
|
220
|
+
value = value.split("/*")[0];
|
|
221
|
+
if (splitOnParen) value = value.split("(")[0];
|
|
222
|
+
value = value.split("[")[0];
|
|
223
|
+
value = value.split("{")[0];
|
|
224
|
+
value = value.split("=")[0];
|
|
225
|
+
value = value.replaceAll("*", "").replaceAll("\n", "");
|
|
226
|
+
return value.split(" ").filter((part) => part.length > 0);
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Port of `is_contain_const_keyword`'s keyword detection: extract a
|
|
230
|
+
* `const`/`static`/`extern`/`#define` constant identifier from one line, or
|
|
231
|
+
* return `null` when the line carries no mixable keyword.
|
|
232
|
+
*/
|
|
233
|
+
function findConstKeyword(line) {
|
|
234
|
+
let working = line;
|
|
235
|
+
if (working.includes(" ")) working = working.replaceAll(" ", " ");
|
|
236
|
+
if (working.length === 0) return null;
|
|
237
|
+
if (working.startsWith("//")) return null;
|
|
238
|
+
if (working.startsWith(" *")) return null;
|
|
239
|
+
if (working.includes("dispatch_once_t")) return null;
|
|
240
|
+
if (working.includes("dispatch_queue_t")) return null;
|
|
241
|
+
if (working.includes("dispatch_main_async_safe")) return null;
|
|
242
|
+
if (working.includes("__VA_ARGS__")) return null;
|
|
243
|
+
if (working.startsWith("#define") && working.trimEnd().endsWith("extern")) return null;
|
|
244
|
+
let lineList;
|
|
245
|
+
if (working.includes("static decltype(")) lineList = splitKeywordLine(working, false);
|
|
246
|
+
else if (working.includes("const decltype(")) lineList = splitKeywordLine(working, false);
|
|
247
|
+
else lineList = splitKeywordLine(working, true);
|
|
248
|
+
let isNeedAddK = false;
|
|
249
|
+
let keyword = null;
|
|
250
|
+
if (lineList.includes("const") && lineList.includes("static")) {
|
|
251
|
+
keyword = lineList[lineList.length - 1];
|
|
252
|
+
isNeedAddK = true;
|
|
253
|
+
} else if (lineList.includes("const") && lineList.includes("extern")) {
|
|
254
|
+
keyword = lineList[lineList.length - 1];
|
|
255
|
+
isNeedAddK = true;
|
|
256
|
+
} else if (lineList.includes("const")) {
|
|
257
|
+
keyword = lineList[lineList.length - 1];
|
|
258
|
+
isNeedAddK = true;
|
|
259
|
+
} else if (lineList.includes("static") && lineList.includes("decltype")) {
|
|
260
|
+
keyword = lineList[lineList.length - 1];
|
|
261
|
+
isNeedAddK = false;
|
|
262
|
+
} else if (lineList.includes("static") && !working.includes(" func ") && !working.includes(" var ") && !working.includes("(")) {
|
|
263
|
+
if (working.includes(":")) keyword = (lineList[lineList.length - 2] ?? "").replaceAll(":", "");
|
|
264
|
+
else keyword = lineList[lineList.length - 1];
|
|
265
|
+
isNeedAddK = false;
|
|
266
|
+
} else if (lineList.includes("extern") && !lineList.includes("\"C\"")) {
|
|
267
|
+
keyword = lineList[lineList.length - 1];
|
|
268
|
+
isNeedAddK = true;
|
|
269
|
+
} else if (working.startsWith("#define ") && lineList.includes("#define")) {
|
|
270
|
+
keyword = lineList[1] ?? null;
|
|
271
|
+
if (keyword !== null) {
|
|
272
|
+
if (keyword.endsWith("_h") || keyword.endsWith("_pch")) keyword = null;
|
|
273
|
+
else {
|
|
274
|
+
isNeedAddK = true;
|
|
275
|
+
if (keyword.endsWith("{")) keyword = keyword.slice(0, -1);
|
|
276
|
+
if (keyword.includes("(")) keyword = keyword.split("(")[0];
|
|
277
|
+
if (keyword.includes(")")) keyword = keyword.split(")")[0];
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
if (keyword === null) return null;
|
|
282
|
+
keyword = keyword.trim().replaceAll("\n", "");
|
|
283
|
+
if (keyword.endsWith(";")) keyword = keyword.slice(0, -1);
|
|
284
|
+
if (keyword.startsWith("*")) keyword = keyword.slice(1);
|
|
285
|
+
if (keyword.includes("[")) keyword = keyword.split("[")[0];
|
|
286
|
+
if (keyword.includes("]")) keyword = keyword.split("]")[0];
|
|
287
|
+
if (working.includes(keyword + "(void)")) keyword = null;
|
|
288
|
+
const judged = keyword === null ? null : judgeKeywordNone(keyword);
|
|
289
|
+
if (judged === null) return null;
|
|
290
|
+
return {
|
|
291
|
+
keyword: judged,
|
|
292
|
+
isNeedAddK
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
const KEYWORD_BOUNDARY_CHARS = [
|
|
296
|
+
"(",
|
|
297
|
+
")",
|
|
298
|
+
" ",
|
|
299
|
+
"||",
|
|
300
|
+
",",
|
|
301
|
+
":",
|
|
302
|
+
"[",
|
|
303
|
+
"]",
|
|
304
|
+
"{",
|
|
305
|
+
"}",
|
|
306
|
+
"&",
|
|
307
|
+
"+",
|
|
308
|
+
"-",
|
|
309
|
+
"*",
|
|
310
|
+
"/",
|
|
311
|
+
"%",
|
|
312
|
+
">",
|
|
313
|
+
"<",
|
|
314
|
+
"=",
|
|
315
|
+
"?",
|
|
316
|
+
"!",
|
|
317
|
+
"\n",
|
|
318
|
+
" ",
|
|
319
|
+
"^",
|
|
320
|
+
";",
|
|
321
|
+
"~",
|
|
322
|
+
"@",
|
|
323
|
+
"."
|
|
324
|
+
];
|
|
325
|
+
/**
|
|
326
|
+
* Port of `replace_keyword_in_line`: replace `oldKeyword` with `mixKeyword`
|
|
327
|
+
* when surrounded by identifier boundaries, skipping property/method uses
|
|
328
|
+
* (`self.foo`, header imports, `@property` declarations).
|
|
329
|
+
*/
|
|
330
|
+
function replaceKeywordInLine(line, oldKeyword, mixKeyword, isXib = false) {
|
|
331
|
+
if (line.includes("%\"" + oldKeyword + "\"")) line = line.replaceAll("%\"" + oldKeyword + "\"", "%\"" + mixKeyword + "\"");
|
|
332
|
+
const boundaries = KEYWORD_BOUNDARY_CHARS.slice();
|
|
333
|
+
if (isXib) {
|
|
334
|
+
const lt = boundaries.indexOf("<");
|
|
335
|
+
if (lt >= 0) boundaries.splice(lt, 1);
|
|
336
|
+
const gt = boundaries.indexOf(">");
|
|
337
|
+
if (gt >= 0) boundaries.splice(gt, 1);
|
|
338
|
+
boundaries.push("\"");
|
|
339
|
+
}
|
|
340
|
+
if (line.startsWith(oldKeyword)) {
|
|
341
|
+
for (const boundary of boundaries) if (line.startsWith(oldKeyword + boundary)) line = line.replaceAll(oldKeyword + boundary, mixKeyword + boundary);
|
|
342
|
+
return line;
|
|
343
|
+
}
|
|
344
|
+
for (const first of boundaries) for (const second of boundaries) {
|
|
345
|
+
const replaceOld = first + oldKeyword + second;
|
|
346
|
+
const replaceMix = first + mixKeyword + second;
|
|
347
|
+
if (line.trim().startsWith("@property") && first === "*" && second === ";") continue;
|
|
348
|
+
if (line.trim().startsWith("@property") && first === " " && second === ";") continue;
|
|
349
|
+
if (first === "+" && second === "." && line.includes(oldKeyword + ".h")) continue;
|
|
350
|
+
if (first === "." && line.includes("self." + oldKeyword)) continue;
|
|
351
|
+
if (first === "." && line.includes("Self." + oldKeyword)) continue;
|
|
352
|
+
line = line.replaceAll(replaceOld, replaceMix);
|
|
353
|
+
}
|
|
354
|
+
return line;
|
|
355
|
+
}
|
|
356
|
+
/** Yield file lines exactly like Python's `for line in open(path)`. */
|
|
357
|
+
function fileLines(content) {
|
|
358
|
+
if (content === "") return [];
|
|
359
|
+
const parts = content.split("\n");
|
|
360
|
+
const lines = [];
|
|
361
|
+
for (let index = 0; index < parts.length - 1; index += 1) lines.push(parts[index] + "\n");
|
|
362
|
+
const last = parts[parts.length - 1];
|
|
363
|
+
if (last !== "") lines.push(last);
|
|
364
|
+
return lines;
|
|
365
|
+
}
|
|
366
|
+
function blockCommentStart(lineNew) {
|
|
367
|
+
return lineNew.startsWith("/*") && !lineNew.endsWith("*/");
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Discover constant keywords in `content`, then replace each with a seeded
|
|
371
|
+
* `prefix`-prefixed name. The same keyword always maps to the same replacement
|
|
372
|
+
* for a given `seed`, so runs are deterministic and idempotent across files.
|
|
373
|
+
*/
|
|
374
|
+
function mixKeywords(content, options) {
|
|
375
|
+
const seed = options.seed;
|
|
376
|
+
const prefix = options.prefix ?? "";
|
|
377
|
+
const mapping = /* @__PURE__ */ new Map();
|
|
378
|
+
const order = [];
|
|
379
|
+
let inBlockComment = false;
|
|
380
|
+
for (const line of fileLines(content)) {
|
|
381
|
+
const lineNew = line.replaceAll(" ", "").replaceAll("\n", "");
|
|
382
|
+
if (blockCommentStart(lineNew)) inBlockComment = true;
|
|
383
|
+
else if (lineNew.startsWith("*/")) inBlockComment = false;
|
|
384
|
+
if (inBlockComment) continue;
|
|
385
|
+
const hit = findConstKeyword(line);
|
|
386
|
+
if (!hit || mapping.has(hit.keyword)) continue;
|
|
387
|
+
const mix = prefix + (hit.isNeedAddK ? "k" : "") + deriveToken(seed, "keyword", hit.keyword);
|
|
388
|
+
mapping.set(hit.keyword, mix);
|
|
389
|
+
order.push(hit.keyword);
|
|
390
|
+
}
|
|
391
|
+
let count = 0;
|
|
392
|
+
inBlockComment = false;
|
|
393
|
+
const output = [];
|
|
394
|
+
for (const line of fileLines(content)) {
|
|
395
|
+
const lineNew = line.replaceAll(" ", "").replaceAll("\n", "");
|
|
396
|
+
if (blockCommentStart(lineNew)) inBlockComment = true;
|
|
397
|
+
else if (lineNew.startsWith("*/")) inBlockComment = false;
|
|
398
|
+
let current = line;
|
|
399
|
+
if (!inBlockComment) for (const oldKeyword of order) {
|
|
400
|
+
if (!current.includes(oldKeyword)) continue;
|
|
401
|
+
const before = current;
|
|
402
|
+
current = replaceKeywordInLine(current, oldKeyword, mapping.get(oldKeyword), false);
|
|
403
|
+
if (current !== before) count += 1;
|
|
404
|
+
}
|
|
405
|
+
output.push(current);
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
content: output.join(""),
|
|
409
|
+
count
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Count source files by extension and return the dominant language plus
|
|
414
|
+
* per-language counts. Ties resolve in the original priority order
|
|
415
|
+
* OC > Swift > C++; an unrecognized set defaults to OC.
|
|
416
|
+
*/
|
|
417
|
+
function detectProjectLanguage(files) {
|
|
418
|
+
const counts = {
|
|
419
|
+
OC: 0,
|
|
420
|
+
Swift: 0,
|
|
421
|
+
Cpp: 0
|
|
422
|
+
};
|
|
423
|
+
for (const file of files) {
|
|
424
|
+
const lower = file.toLowerCase();
|
|
425
|
+
if (lower.endsWith(".m") || lower.endsWith(".mm")) counts.OC += 1;
|
|
426
|
+
else if (lower.endsWith(".swift")) counts.Swift += 1;
|
|
427
|
+
else if (lower.endsWith(".cpp") || lower.endsWith(".cc") || lower.endsWith(".cxx") || lower.endsWith(".c") || lower.endsWith(".hpp")) counts.Cpp += 1;
|
|
428
|
+
}
|
|
429
|
+
let language = "OC";
|
|
430
|
+
if (counts.Swift > counts.OC && counts.Swift >= counts.Cpp) language = "Swift";
|
|
431
|
+
else if (counts.Cpp > counts.OC && counts.Cpp > counts.Swift) language = "C++";
|
|
432
|
+
return {
|
|
433
|
+
language,
|
|
434
|
+
counts
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
function countQuotes(value) {
|
|
438
|
+
return countOccurrences(value, "\"");
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Port of `delete_two_line`: strip `//` comments from a line while respecting
|
|
442
|
+
* quote balancing across `//` segments.
|
|
443
|
+
*/
|
|
444
|
+
function deleteTwoLineComment(line) {
|
|
445
|
+
if (line.trim().startsWith("//")) line = "";
|
|
446
|
+
else {
|
|
447
|
+
const newLineList = [];
|
|
448
|
+
let count = 0;
|
|
449
|
+
const lineList = line.split("//");
|
|
450
|
+
if (lineList.length === 2) line = lineList[0];
|
|
451
|
+
else {
|
|
452
|
+
for (let index = 0; index < lineList.length; index += 1) {
|
|
453
|
+
const linePart = line.split("//")[index];
|
|
454
|
+
count += countQuotes(linePart);
|
|
455
|
+
if (count <= 1) newLineList.push(linePart);
|
|
456
|
+
if (count === 2) newLineList.push(linePart);
|
|
457
|
+
}
|
|
458
|
+
line = newLineList.join("//");
|
|
459
|
+
}
|
|
460
|
+
if (line.length > 0) line += "\n";
|
|
461
|
+
}
|
|
462
|
+
return line;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Port of `delete_line_stars`: remove `/** ... * /` doc blocks and continuous
|
|
466
|
+
* star-comment lines, updating the shared block-comment state.
|
|
467
|
+
*/
|
|
468
|
+
function deleteLineStars(line, state) {
|
|
469
|
+
if (!line.includes("*/")) state.inBlockComment = true;
|
|
470
|
+
else state.inBlockComment = false;
|
|
471
|
+
const captured = [];
|
|
472
|
+
for (const match of line.matchAll(/[/\*](.*?)[\*\/]/g)) captured.push(match[1] ?? "");
|
|
473
|
+
const resultList = captured.filter((item) => item.length > 0);
|
|
474
|
+
for (const item of resultList) line = line.split("/**" + item + "*/").join("");
|
|
475
|
+
if (line.trim().startsWith("/*")) line = line.slice(0, line.indexOf("/*"));
|
|
476
|
+
else {
|
|
477
|
+
const newLineList = [];
|
|
478
|
+
let count = 0;
|
|
479
|
+
const parts = line.split("/*");
|
|
480
|
+
for (let index = 0; index < parts.length; index += 1) {
|
|
481
|
+
const linePart = parts[index];
|
|
482
|
+
count += countQuotes(linePart);
|
|
483
|
+
if (count <= 1) newLineList.push(linePart);
|
|
484
|
+
if (count === 2) newLineList.push(linePart);
|
|
485
|
+
}
|
|
486
|
+
line = newLineList.join("//");
|
|
487
|
+
}
|
|
488
|
+
if (line.includes("//")) line = line.split("//")[0];
|
|
489
|
+
if (line.length > 0) line += "\n";
|
|
490
|
+
return line + "\n";
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Port of `delete_notes_in_every_line`. Returns `null` for lines that should
|
|
494
|
+
* be dropped (interior of a multi-line block comment).
|
|
495
|
+
*/
|
|
496
|
+
function deleteNotesInLine(line, state) {
|
|
497
|
+
if (line.startsWith("#pragma once")) return line;
|
|
498
|
+
if (state.inBlockComment && !line.includes("*/")) return null;
|
|
499
|
+
if (state.inBlockComment && line.includes("*/")) {
|
|
500
|
+
state.inBlockComment = false;
|
|
501
|
+
line = line.slice(line.indexOf("*/") + 2);
|
|
502
|
+
if (line.length > 0) line += "\n";
|
|
503
|
+
return line;
|
|
504
|
+
}
|
|
505
|
+
const resultList = line.match(/\/\*{1,2}[\s\S]*?\*\//g) ?? [];
|
|
506
|
+
if (resultList.length > 0) for (const item of resultList) line = line.replaceAll(item, " ");
|
|
507
|
+
if (line.trim().startsWith("/*") && !line.trim().endsWith("*/") && line.split("//").length >= 2) line = line.split("//")[0];
|
|
508
|
+
if (line.trim().startsWith("/*") && line.trim().endsWith("*/") && line.includes("//")) line = line.replaceAll("//", " ");
|
|
509
|
+
if (line.trim().startsWith("//")) line = "";
|
|
510
|
+
if (line.includes("/*") && !line.includes("/*\"")) line = deleteLineStars(line, state);
|
|
511
|
+
if (line.includes("//") && !line.includes("://") && !line.trim().endsWith("*/")) line = deleteTwoLineComment(line);
|
|
512
|
+
if (line.trim().startsWith("#pragma") && !line.trim().startsWith("#pragma pack(")) line = "";
|
|
513
|
+
if (line.trim().startsWith("#warning")) line = "";
|
|
514
|
+
return line;
|
|
515
|
+
}
|
|
516
|
+
/** Remove ObjC comments from a whole source file's contents. */
|
|
517
|
+
function deleteObjCNotes(content) {
|
|
518
|
+
const state = { inBlockComment: false };
|
|
519
|
+
let output = "";
|
|
520
|
+
for (const line of fileLines(content)) {
|
|
521
|
+
const result = deleteNotesInLine(line, state);
|
|
522
|
+
if (result !== null && result.length > 0) output += result;
|
|
523
|
+
}
|
|
524
|
+
return output;
|
|
525
|
+
}
|
|
526
|
+
export { DFAFilter, deleteLineStars, deleteNotesInLine, deleteObjCNotes, deleteTwoLineComment, detectProjectLanguage, findConstKeyword, hasSpecialString, judgeKeywordNone, mixKeywords, replaceKeywordInLine };
|
|
527
|
+
|
|
528
|
+
//# sourceMappingURL=dfa.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dfa.js","names":["oldLine"],"sources":["../src/dfa.ts"],"sourcesContent":["/**\n * Deterministic DFA sensitive-word filter and the algorithms that ship next\n * to it: constant-keyword mixing, project language detection, and ObjC comment\n * removal. GUI, networking and authorization code is intentionally dropped.\n *\n * Everything here is pure and deterministic; keyword names are derived with the\n * seeded helpers from ./common.js, never with Math.random or Date.now.\n */\nimport { deriveToken } from './common.js';\nimport { filterList, wordFilterList } from './lexicon-data.js';\n\n// ---------------------------------------------------------------------------\n// DFA sensitive-word filter\n// ---------------------------------------------------------------------------\n\n/** Word-termination marker stored inside trie nodes. */\nconst DELIMIT = '\\u0000';\n\n/**\n * A trie node maps each character to a child node, and the special `DELIMIT`\n * key to `0` when a sensitive word ends at this node.\n */\ninterface TrieNode {\n [char: string]: TrieNode | 0;\n}\n\n/**\n * Standard DFA (dictionary-tree) sensitive-word filter.\n *\n * Word lists are lower-cased before insertion, matching the original\n * `addSensitiveWords`; messages are scanned as-is. `filter` replaces each hit\n * with `repl` repeated to the length of the matched word and leaves unmatched\n * text unchanged.\n */\nexport class DFAFilter {\n private readonly keywordChains: TrieNode = {};\n private readonly delimit = DELIMIT;\n\n constructor(wordList?: Iterable<string>) {\n this.parseSensitiveWords(wordList);\n }\n\n private parseSensitiveWords(wordList?: Iterable<string>): void {\n const words = wordList ?? wordFilterList;\n for (const word of words) this.addSensitiveWords(String(word).trim());\n }\n\n /** Insert one sensitive word into the trie (lower-cased and trimmed). */\n addSensitiveWords(keyword: string): void {\n const chars = keyword.toLowerCase().trim();\n if (!chars) return;\n let level: TrieNode = this.keywordChains;\n for (const char of chars) {\n let child = level[char];\n if (typeof child !== 'object') {\n child = {};\n level[char] = child;\n }\n level = child;\n }\n level[this.delimit] = 0;\n }\n\n /**\n * Replace every sensitive word with `repl` repeated to the matched word's\n * length. Unmatched text is preserved verbatim.\n */\n filter(message: string, repl = '*'): string {\n const out: string[] = [];\n let start = 0;\n while (start < message.length) {\n let level: TrieNode = this.keywordChains;\n let cursor = start;\n let matchEnd = -1;\n while (cursor < message.length) {\n const child = level[message[cursor]!];\n if (typeof child !== 'object') break;\n level = child;\n cursor += 1;\n if (this.delimit in level) matchEnd = cursor;\n }\n if (matchEnd >= 0) {\n out.push(repl.repeat(matchEnd - start));\n start = matchEnd;\n } else {\n out.push(message[start]!);\n start += 1;\n }\n }\n return out.join('');\n }\n}\n\n// ---------------------------------------------------------------------------\n// Constant-keyword mixing\n// ---------------------------------------------------------------------------\n\nconst MIN_KEYWORD_LENGTH = 7;\n\n/** ASCII punctuation without `_` (matches `string.punctuation` minus `_`). */\nconst SPECIAL_PUNCTUATION_CHARS = \"!\\\"#$%&'()*+,-./:;<=>?@[\\\\]^`{|}~\";\n\nfunction countOccurrences(value: string, char: string): number {\n let count = 0;\n for (const current of value) if (current === char) count += 1;\n return count;\n}\n\n/** True when `value` contains any ASCII punctuation other than `_`. */\nexport function hasSpecialString(value: string): boolean {\n for (const char of value) if (SPECIAL_PUNCTUATION_CHARS.includes(char)) return true;\n return false;\n}\n\nconst KEYWORD_IGNORE_LIST: readonly string[] = [\n 'instance', 'onceToken', 'types', 'WEAKBLOCK', 'ClassMethod', 'manager',\n 'diagonal', 'deltas', 'result', 'value', 'className', 'multiplier',\n 'decibel', 'platform', 'filePath', 'source', 'identifier', 'components',\n 'conversion', 'calendar', 'dateFormatter', 'operationKey', 'componentFlags',\n 'nullable', 'nonnull', 'gregorian', 'processPool', 'stringValue', 'encoding',\n 'reuseIdentifier', 'locations', 'RotQuatG', 'iterator', 'gQuatRot',\n 'HAVE_AES', 'colorSpace', 'contentOffset', 'aapssView',\n];\n\n/**\n * Port of `judge_keyword_none`: returns `null` when a candidate keyword must be\n * skipped (too short, system/third-party prefix, common property/method name,\n * or listed in the filter list), otherwise the keyword itself.\n */\nexport function judgeKeywordNone(keyword: string): string | null {\n if (keyword.length <= MIN_KEYWORD_LENGTH) return null;\n if (keyword.includes('ScreenWidth') || keyword.includes('SCREEN_WIDTH')) return null;\n if (keyword.includes('ScreenHeight') || keyword.includes('SCREEN_HEIGHT')) return null;\n if (keyword.startsWith('once')) return null;\n if (keyword.startsWith('CG')) return null;\n if (keyword.startsWith('NS')) return null;\n if (keyword.startsWith('UI')) return null;\n if (keyword.startsWith('UTF')) return null;\n if (keyword.startsWith('BN_')) return null;\n if (keyword.startsWith('SDWebImage')) return null;\n if (keyword.startsWith('PLATFORM_')) return null;\n if (keyword.startsWith('print')) return null;\n if (keyword.startsWith('UNITY') || keyword.startsWith('Unity')) return null;\n if (keyword.startsWith('APP_CONTROLLER')) return null;\n if (keyword.startsWith('s_')) return null;\n if (keyword.startsWith('count')) return null;\n if (keyword.startsWith('_')) return null;\n if (keyword.startsWith('shared')) return null;\n if (keyword.startsWith('sd_')) return null;\n if (keyword.startsWith('SDImage')) return null;\n if (keyword.startsWith('metamacro_')) return null;\n if (keyword.startsWith('kCGImage')) return null;\n if (keyword.startsWith('YYCGImage')) return null;\n if (keyword.startsWith('MJRefresh')) return null;\n if (keyword.includes('_shared')) return null;\n if (keyword.includes('Base64Encode')) return null;\n if (keyword.includes('md5Key')) return null;\n if (keyword.includes('Instance') || keyword.includes('instance')) return null;\n if (keyword.includes('Assert') || keyword.includes('assert')) return null;\n if (keyword.startsWith('block')) return null;\n if (keyword.startsWith('and')) return null;\n if (keyword.startsWith('NSLog')) return null;\n if (keyword.startsWith('GLES_')) return null;\n if (keyword.startsWith('GTM_')) return null;\n if (keyword.startsWith('CF_')) return null;\n if (keyword.startsWith('SSL3_')) return null;\n if (keyword.startsWith('TLS1_')) return null;\n if (keyword.startsWith('kCVPixel')) return null;\n if (keyword.startsWith('set') || keyword.startsWith('Set')) return null;\n if (keyword.startsWith('get') || keyword.startsWith('Get')) return null;\n if (keyword.startsWith('public_') || keyword.startsWith('PUBLIC_')) return null;\n if (keyword.startsWith('DETAIL_') || keyword.startsWith('Detail_')) return null;\n if (keyword.startsWith('Exchange')) return null;\n if (keyword.startsWith('Compare')) return null;\n if (keyword.startsWith('Increment')) return null;\n if (keyword.startsWith('Decrement')) return null;\n if (keyword.startsWith('IL2CPP_') || keyword.startsWith('il2cpp_')) return null;\n if (keyword.startsWith('detail_')) return null;\n if (keyword.startsWith('Baselib_')) return null;\n if (keyword.startsWith('DEBUG') || keyword.endsWith('DEBUG')) return null;\n if (keyword.startsWith('init')) return null;\n if (keyword.startsWith('Log') || keyword.startsWith('log') || keyword.startsWith('LOG')) return null;\n if (keyword.endsWith('log') || keyword.endsWith('Log') || keyword.endsWith('LOG')) return null;\n if (keyword.includes('Log')) return null;\n if (keyword.includes('_get_')) return null;\n if (keyword.includes('_set_')) return null;\n if (keyword.includes('Class') || keyword.includes('class')) return null;\n if (keyword.endsWith('_SIZE') || keyword.endsWith('_size') || keyword.endsWith('Size')) return null;\n if (keyword.endsWith('DEBUG') || keyword.endsWith('Debug')) return null;\n if (keyword.endsWith('_var')) return null;\n if (keyword.endsWith('Internal')) return null;\n if (keyword.startsWith('Internal')) return null;\n if (keyword.startsWith('RCKit')) return null;\n if (keyword.startsWith('MD5_')) return null;\n if (keyword.startsWith('EVP_')) return null;\n if (keyword.startsWith('NID_')) return null;\n if (keyword.startsWith('VERSION') || keyword.startsWith('Version')) return null;\n if (keyword.startsWith('METHOD_')) return null;\n if (keyword.startsWith('COMPILER_')) return null;\n if (keyword.startsWith('BASELIB') || keyword.startsWith('Baselib')) return null;\n if (KEYWORD_IGNORE_LIST.includes(keyword)) return null;\n if (keyword.includes('_')) {\n const parts = keyword.split('_');\n if (parts[parts.length - 1]!.length === 1) return null;\n if (parts[0]!.length === 1) return null;\n }\n if (keyword.includes('String') && keyword.replaceAll('String', '').length <= 3) return null;\n if (keyword.includes('string') && keyword.replaceAll('string', '').length <= 3) return null;\n if (hasSpecialString(keyword)) return null;\n if (filterList.includes(keyword)) return null;\n return keyword;\n}\n\nexport interface ConstKeywordHit {\n keyword: string;\n isNeedAddK: boolean;\n}\n\n/** Split a source line the way `is_contain_const_keyword` does before keyword selection. */\nfunction splitKeywordLine(line: string, splitOnParen: boolean): string[] {\n let value = line.split('//')[0]!;\n value = value.split('/*')[0]!;\n if (splitOnParen) value = value.split('(')[0]!;\n value = value.split('[')[0]!;\n value = value.split('{')[0]!;\n value = value.split('=')[0]!;\n value = value.replaceAll('*', '').replaceAll('\\n', '');\n return value.split(' ').filter(part => part.length > 0);\n}\n\n/**\n * Port of `is_contain_const_keyword`'s keyword detection: extract a\n * `const`/`static`/`extern`/`#define` constant identifier from one line, or\n * return `null` when the line carries no mixable keyword.\n */\nexport function findConstKeyword(line: string): ConstKeywordHit | null {\n let working = line;\n if (working.includes('\\t')) working = working.replaceAll('\\t', ' ');\n if (working.length === 0) return null;\n if (working.startsWith('//')) return null;\n if (working.startsWith(' *')) return null;\n if (working.includes('dispatch_once_t')) return null;\n if (working.includes('dispatch_queue_t')) return null;\n if (working.includes('dispatch_main_async_safe')) return null;\n if (working.includes('__VA_ARGS__')) return null;\n if (working.startsWith('#define') && working.trimEnd().endsWith('extern')) return null;\n\n let lineList: string[];\n if (working.includes('static decltype(')) lineList = splitKeywordLine(working, false);\n else if (working.includes('const decltype(')) lineList = splitKeywordLine(working, false);\n else lineList = splitKeywordLine(working, true);\n\n let isNeedAddK = false;\n let keyword: string | null = null;\n\n if (lineList.includes('const') && lineList.includes('static')) {\n keyword = lineList[lineList.length - 1]!;\n isNeedAddK = true;\n } else if (lineList.includes('const') && lineList.includes('extern')) {\n keyword = lineList[lineList.length - 1]!;\n isNeedAddK = true;\n } else if (lineList.includes('const')) {\n keyword = lineList[lineList.length - 1]!;\n isNeedAddK = true;\n } else if (lineList.includes('static') && lineList.includes('decltype')) {\n keyword = lineList[lineList.length - 1]!;\n isNeedAddK = false;\n } else if (lineList.includes('static') && !working.includes(' func ') && !working.includes(' var ') && !working.includes('(')) {\n if (working.includes(':')) keyword = (lineList[lineList.length - 2] ?? '').replaceAll(':', '');\n else keyword = lineList[lineList.length - 1]!;\n isNeedAddK = false;\n } else if (lineList.includes('extern') && !lineList.includes('\"C\"')) {\n keyword = lineList[lineList.length - 1]!;\n isNeedAddK = true;\n } else if (working.startsWith('#define ') && lineList.includes('#define')) {\n keyword = lineList[1] ?? null;\n if (keyword !== null) {\n if (keyword.endsWith('_h') || keyword.endsWith('_pch')) {\n keyword = null;\n } else {\n isNeedAddK = true;\n if (keyword.endsWith('{')) keyword = keyword.slice(0, -1);\n if (keyword.includes('(')) keyword = keyword.split('(')[0]!;\n if (keyword.includes(')')) keyword = keyword.split(')')[0]!;\n }\n }\n }\n\n if (keyword === null) return null;\n keyword = keyword.trim().replaceAll('\\n', '');\n if (keyword.endsWith(';')) keyword = keyword.slice(0, -1);\n if (keyword.startsWith('*')) keyword = keyword.slice(1);\n if (keyword.includes('[')) keyword = keyword.split('[')[0]!;\n if (keyword.includes(']')) keyword = keyword.split(']')[0]!;\n if (working.includes(keyword + '(void)')) keyword = null;\n\n const judged = keyword === null ? null : judgeKeywordNone(keyword);\n if (judged === null) return null;\n return { keyword: judged, isNeedAddK };\n}\n\nconst KEYWORD_BOUNDARY_CHARS = [\n '(', ')', ' ', '||', ',', ':', '[', ']', '{', '}', '&', '+', '-', '*', '/',\n '%', '>', '<', '=', '?', '!', '\\n', '\\t', '^', ';', '~', '@', '.',\n];\n\n/**\n * Port of `replace_keyword_in_line`: replace `oldKeyword` with `mixKeyword`\n * when surrounded by identifier boundaries, skipping property/method uses\n * (`self.foo`, header imports, `@property` declarations).\n */\nexport function replaceKeywordInLine(line: string, oldKeyword: string, mixKeyword: string, isXib = false): string {\n if (line.includes('%\"' + oldKeyword + '\"')) {\n line = line.replaceAll('%\"' + oldKeyword + '\"', '%\"' + mixKeyword + '\"');\n }\n\n const boundaries = KEYWORD_BOUNDARY_CHARS.slice();\n if (isXib) {\n const lt = boundaries.indexOf('<');\n if (lt >= 0) boundaries.splice(lt, 1);\n const gt = boundaries.indexOf('>');\n if (gt >= 0) boundaries.splice(gt, 1);\n boundaries.push('\"');\n }\n\n if (line.startsWith(oldKeyword)) {\n for (const boundary of boundaries) {\n if (line.startsWith(oldKeyword + boundary)) {\n line = line.replaceAll(oldKeyword + boundary, mixKeyword + boundary);\n }\n }\n return line;\n }\n\n for (const first of boundaries) {\n for (const second of boundaries) {\n const replaceOld = first + oldKeyword + second;\n const replaceMix = first + mixKeyword + second;\n if (line.trim().startsWith('@property') && first === '*' && second === ';') continue;\n if (line.trim().startsWith('@property') && first === ' ' && second === ';') continue;\n if (first === '+' && second === '.' && line.includes(oldKeyword + '.h')) continue;\n if (first === '.' && line.includes('self.' + oldKeyword)) continue;\n if (first === '.' && line.includes('Self.' + oldKeyword)) continue;\n line = line.replaceAll(replaceOld, replaceMix);\n }\n }\n return line;\n}\n\nexport interface KeywordMixOptions {\n /** Seed for deterministic replacement-name generation. */\n seed: string;\n /** Optional identifier prefix prepended to every generated name. */\n prefix?: string;\n}\n\nexport interface KeywordMixResult {\n /** Content with mixable keywords replaced. */\n content: string;\n /** Number of keyword hits (keyword occurrences replaced on distinct lines). */\n count: number;\n}\n\n/** Yield file lines exactly like Python's `for line in open(path)`. */\nfunction fileLines(content: string): string[] {\n if (content === '') return [];\n const parts = content.split('\\n');\n const lines: string[] = [];\n for (let index = 0; index < parts.length - 1; index += 1) lines.push(parts[index]! + '\\n');\n const last = parts[parts.length - 1]!;\n if (last !== '') lines.push(last);\n return lines;\n}\n\nfunction blockCommentStart(lineNew: string): boolean {\n return lineNew.startsWith('/*') && !lineNew.endsWith('*/');\n}\n\n/**\n * Discover constant keywords in `content`, then replace each with a seeded\n * `prefix`-prefixed name. The same keyword always maps to the same replacement\n * for a given `seed`, so runs are deterministic and idempotent across files.\n */\nexport function mixKeywords(content: string, options: KeywordMixOptions): KeywordMixResult {\n const seed = options.seed;\n const prefix = options.prefix ?? '';\n const mapping = new Map<string, string>();\n const order: string[] = [];\n\n // Pass 1: discover candidates, skipping block-comment regions.\n let inBlockComment = false;\n for (const line of fileLines(content)) {\n const lineNew = line.replaceAll(' ', '').replaceAll('\\n', '');\n if (blockCommentStart(lineNew)) inBlockComment = true;\n else if (lineNew.startsWith('*/')) inBlockComment = false;\n if (inBlockComment) continue;\n const hit = findConstKeyword(line);\n if (!hit || mapping.has(hit.keyword)) continue;\n const mix = prefix + (hit.isNeedAddK ? 'k' : '') + deriveToken(seed, 'keyword', hit.keyword);\n mapping.set(hit.keyword, mix);\n order.push(hit.keyword);\n }\n\n // Pass 2: replace discovered keywords in source order.\n let count = 0;\n inBlockComment = false;\n const output: string[] = [];\n for (const line of fileLines(content)) {\n const lineNew = line.replaceAll(' ', '').replaceAll('\\n', '');\n if (blockCommentStart(lineNew)) inBlockComment = true;\n else if (lineNew.startsWith('*/')) inBlockComment = false;\n let current = line;\n if (!inBlockComment) {\n for (const oldKeyword of order) {\n if (!current.includes(oldKeyword)) continue;\n const before = current;\n current = replaceKeywordInLine(current, oldKeyword, mapping.get(oldKeyword)!, false);\n if (current !== before) count += 1;\n }\n }\n output.push(current);\n }\n\n return { content: output.join(''), count };\n}\n\n// ---------------------------------------------------------------------------\n// Project development language detection\n// ---------------------------------------------------------------------------\n\nexport type DetectedLanguage = 'OC' | 'Swift' | 'C++';\n\nexport interface LanguageCounts {\n OC: number;\n Swift: number;\n Cpp: number;\n}\n\nexport interface LanguageDetection {\n language: DetectedLanguage;\n counts: LanguageCounts;\n}\n\n/**\n * Count source files by extension and return the dominant language plus\n * per-language counts. Ties resolve in the original priority order\n * OC > Swift > C++; an unrecognized set defaults to OC.\n */\nexport function detectProjectLanguage(files: readonly string[]): LanguageDetection {\n const counts: LanguageCounts = { OC: 0, Swift: 0, Cpp: 0 };\n for (const file of files) {\n const lower = file.toLowerCase();\n if (lower.endsWith('.m') || lower.endsWith('.mm')) counts.OC += 1;\n else if (lower.endsWith('.swift')) counts.Swift += 1;\n else if (lower.endsWith('.cpp') || lower.endsWith('.cc') || lower.endsWith('.cxx') || lower.endsWith('.c') || lower.endsWith('.hpp')) counts.Cpp += 1;\n }\n\n let language: DetectedLanguage = 'OC';\n if (counts.Swift > counts.OC && counts.Swift >= counts.Cpp) language = 'Swift';\n else if (counts.Cpp > counts.OC && counts.Cpp > counts.Swift) language = 'C++';\n return { language, counts };\n}\n\n// ---------------------------------------------------------------------------\n// ObjC comment removal\n// ---------------------------------------------------------------------------\n\nexport interface NotesState {\n inBlockComment: boolean;\n}\n\nfunction countQuotes(value: string): number {\n return countOccurrences(value, '\"');\n}\n\n/**\n * Port of `delete_two_line`: strip `//` comments from a line while respecting\n * quote balancing across `//` segments.\n */\nexport function deleteTwoLineComment(line: string): string {\n if (line.trim().startsWith('//')) {\n line = '';\n } else {\n const newLineList: string[] = [];\n let count = 0;\n const lineList = line.split('//');\n if (lineList.length === 2) {\n line = lineList[0]!;\n } else {\n for (let index = 0; index < lineList.length; index += 1) {\n const linePart = line.split('//')[index]!;\n count += countQuotes(linePart);\n if (count <= 1) newLineList.push(linePart);\n if (count === 2) newLineList.push(linePart);\n }\n line = newLineList.join('//');\n }\n if (line.length > 0) line += '\\n';\n }\n return line;\n}\n\n/**\n * Port of `delete_line_stars`: remove `/** ... * /` doc blocks and continuous\n * star-comment lines, updating the shared block-comment state.\n */\nexport function deleteLineStars(line: string, state: NotesState): string {\n if (!line.includes('*/')) state.inBlockComment = true;\n else state.inBlockComment = false;\n\n const captured: string[] = [];\n for (const match of line.matchAll(/[/\\*](.*?)[\\*\\/]/g)) captured.push(match[1] ?? '');\n const resultList = captured.filter(item => item.length > 0);\n for (const item of resultList) line = line.split('/**' + item + '*/').join('');\n\n if (line.trim().startsWith('/*')) {\n line = line.slice(0, line.indexOf('/*'));\n } else {\n const newLineList: string[] = [];\n let count = 0;\n const parts = line.split('/*');\n for (let index = 0; index < parts.length; index += 1) {\n const linePart = parts[index]!;\n count += countQuotes(linePart);\n if (count <= 1) newLineList.push(linePart);\n if (count === 2) newLineList.push(linePart);\n }\n line = newLineList.join('//');\n }\n if (line.includes('//')) line = line.split('//')[0]!;\n if (line.length > 0) line += '\\n';\n return line + '\\n';\n}\n\n/**\n * Port of `delete_notes_in_every_line`. Returns `null` for lines that should\n * be dropped (interior of a multi-line block comment).\n */\nexport function deleteNotesInLine(line: string, state: NotesState): string | null {\n const oldLine = line;\n if (oldLine.startsWith('#pragma once')) return line;\n if (state.inBlockComment && !line.includes('*/')) return null;\n if (state.inBlockComment && line.includes('*/')) {\n state.inBlockComment = false;\n line = line.slice(line.indexOf('*/') + 2);\n if (line.length > 0) line += '\\n';\n return line;\n }\n\n const resultList = line.match(/\\/\\*{1,2}[\\s\\S]*?\\*\\//g) ?? [];\n if (resultList.length > 0) {\n for (const item of resultList) line = line.replaceAll(item, ' ');\n }\n\n if (line.trim().startsWith('/*') && !line.trim().endsWith('*/') && line.split('//').length >= 2) {\n line = line.split('//')[0]!;\n }\n if (line.trim().startsWith('/*') && line.trim().endsWith('*/') && line.includes('//')) {\n line = line.replaceAll('//', ' ');\n }\n if (line.trim().startsWith('//')) line = '';\n if (line.includes('/*') && !line.includes('/*\"')) {\n line = deleteLineStars(line, state);\n }\n if (line.includes('//') && !line.includes('://') && !line.trim().endsWith('*/')) {\n line = deleteTwoLineComment(line);\n }\n if (line.trim().startsWith('#pragma') && !line.trim().startsWith('#pragma pack(')) line = '';\n if (line.trim().startsWith('#warning')) line = '';\n return line;\n}\n\n/** Remove ObjC comments from a whole source file's contents. */\nexport function deleteObjCNotes(content: string): string {\n const state: NotesState = { inBlockComment: false };\n let output = '';\n for (const line of fileLines(content)) {\n const result = deleteNotesInLine(line, state);\n if (result !== null && result.length > 0) output += result;\n }\n return output;\n}\n"],"mappings":"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"}
|