@kezh/my-custom-js-converter 1.6.3 → 1.8.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/core/decode/index.js +12 -1
- package/core/encode/index.js +48 -0
- package/core/util/ast.js +241 -4
- package/package.json +1 -1
package/core/decode/index.js
CHANGED
|
@@ -97,7 +97,18 @@ function decoding$0(inPath, outPath, config, settingDir, libDirs, conflicts) {
|
|
|
97
97
|
mapContext.writeToLocalDir(settingDir);
|
|
98
98
|
}
|
|
99
99
|
(0, ast_1.importMods)(path_1.default.dirname(outPath), srcAst, mapContext, config.encode.useAlias);
|
|
100
|
-
|
|
100
|
+
// 稽核内联工具方法体是否与源文件一致(忽略注释)
|
|
101
|
+
const mismatches = (0, ast_1.auditModMethods)(srcAst, mapContext);
|
|
102
|
+
const skipMethodKeys = new Set();
|
|
103
|
+
if (mismatches.length) {
|
|
104
|
+
logger.warn(`检测到 ${mismatches.length} 个内联工具方法与源文件不一致,将保留其内联形态不还原:`);
|
|
105
|
+
mismatches.forEach((m) => {
|
|
106
|
+
logger.warn(` - ${m.methodKey}(源文件: ${m.srcPath})`);
|
|
107
|
+
skipMethodKeys.add(m.methodKey);
|
|
108
|
+
});
|
|
109
|
+
logger.warn(`请检查上述方法,确认 coded.js 与源文件差异后手动处理或重新 encode。`);
|
|
110
|
+
}
|
|
111
|
+
(0, ast_1.deleteModMethods)(srcAst, mapContext, skipMethodKeys);
|
|
101
112
|
// 从onFormReady方法中提取版本日志并移除
|
|
102
113
|
extractAndRemoveVersionLog(srcAst, settingDir);
|
|
103
114
|
let { code: dist } = (0, generator_1.default)(srcAst, { compact: true });
|
package/core/encode/index.js
CHANGED
|
@@ -11,6 +11,7 @@ const map_context_1 = require("../context/map_context");
|
|
|
11
11
|
const ast_1 = require("../util/ast");
|
|
12
12
|
const version_1 = require("../util/version");
|
|
13
13
|
const core_1 = require("@babel/core");
|
|
14
|
+
const parent_path_1 = require("../util/parent_path");
|
|
14
15
|
const logger_1 = require("../util/logger");
|
|
15
16
|
const logger = (0, logger_1.createLogger)("encode");
|
|
16
17
|
(function encoding(config) {
|
|
@@ -26,6 +27,13 @@ const logger = (0, logger_1.createLogger)("encode");
|
|
|
26
27
|
const settingDir = path_1.default.join(workDir, config.settingDir);
|
|
27
28
|
logger.step(`编码处理 [${index + 1}/${codeFiles.length}]`);
|
|
28
29
|
logger.info(`源文件: ${inPath}`);
|
|
30
|
+
// 防护检测:源文件 mount 对象内若残留 __mod 后缀方法,说明用户复用了 decode 产物
|
|
31
|
+
// 作为二开源文件。此时禁止通过 require 引入工具方法,只做无依赖编码。
|
|
32
|
+
const guardResult = guardInlinedModMethods(inPath, encodeConfig.entry, config);
|
|
33
|
+
if (guardResult) {
|
|
34
|
+
logger.error(guardResult);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
29
37
|
const mapContext = new map_context_1.MapContext(inPath, encodeConfig.entry, settingDir, true);
|
|
30
38
|
const methods = encoding$0(mapContext, encodeConfig.entry);
|
|
31
39
|
totalMethods += methods.length;
|
|
@@ -109,3 +117,43 @@ function reorderMethods(methods) {
|
|
|
109
117
|
methods.length = 0;
|
|
110
118
|
methods.push(...priorityMethods, ...restMethods);
|
|
111
119
|
}
|
|
120
|
+
/**
|
|
121
|
+
* 防护检测:源文件 mount 对象内若残留 `__mod` 后缀方法,且同时通过 require 引入了
|
|
122
|
+
* 工具方法依赖,则禁止编码。
|
|
123
|
+
*
|
|
124
|
+
* 场景:用户把 decode 产物(含未还原的 `validate__util` 等内联方法)复制为 index.js
|
|
125
|
+
* 进行二开,同时又保留了 `const util = require(...)`。此时若继续 encode,会导致
|
|
126
|
+
* 方法重复定义、mod.map 错乱。应提示用户移除 require 引入,只做无依赖编码。
|
|
127
|
+
*
|
|
128
|
+
* @param inPath - 源文件路径
|
|
129
|
+
* @param entry - 入口对象名
|
|
130
|
+
* @param config - 主配置
|
|
131
|
+
* @returns 错误提示字符串(检测到连规时返回,未连规返回 null)
|
|
132
|
+
*/
|
|
133
|
+
function guardInlinedModMethods(inPath, entry, config) {
|
|
134
|
+
const srcAst = (0, ast_1.parseSrcAst)(inPath);
|
|
135
|
+
if (!srcAst) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
const inlinedNames = (0, ast_1.getInlinedModMethodNames)(srcAst, entry);
|
|
139
|
+
if (!inlinedNames.length) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
// 检测是否存在 require 引入的工具方法依赖
|
|
143
|
+
const rootDir = config.encode.useAlias
|
|
144
|
+
? (0, parent_path_1.getParentRootDir)() || config.baseDir
|
|
145
|
+
: undefined;
|
|
146
|
+
const requireModPaths = (0, ast_1.getRequireModPaths)(path_1.default.dirname(inPath), srcAst, undefined, rootDir);
|
|
147
|
+
if (!requireModPaths.size) {
|
|
148
|
+
// 有 __mod 方法但无 require 依赖:允许无依赖编码
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
const depNames = [...requireModPaths.keys()];
|
|
152
|
+
return (`检测到源文件 mount 对象内残留工具方法后缀(${inlinedNames.join(", ")}),` +
|
|
153
|
+
`同时存在 require 依赖引入(${depNames.join(", ")})。\n` +
|
|
154
|
+
`此场景不允许编码:请移除源文件中的 require 工具方法引入(${depNames
|
|
155
|
+
.map((n) => `const ${n} = require(...)`)
|
|
156
|
+
.join("; ")}),` +
|
|
157
|
+
`仅保留 mount 对象内的 __mod 后缀方法做无依赖编码;` +
|
|
158
|
+
`或将 __mod 后缀方法抽为独立工具文件后再 encode。`);
|
|
159
|
+
}
|
package/core/util/ast.js
CHANGED
|
@@ -16,6 +16,8 @@ exports.importMods = importMods;
|
|
|
16
16
|
exports.deleteModMethods = deleteModMethods;
|
|
17
17
|
exports.getInlineMethodsByMethodName = getInlineMethodsByMethodName;
|
|
18
18
|
exports.getUnknownDepNames = getUnknownDepNames;
|
|
19
|
+
exports.auditModMethods = auditModMethods;
|
|
20
|
+
exports.getInlinedModMethodNames = getInlinedModMethodNames;
|
|
19
21
|
const fs_1 = __importDefault(require("fs"));
|
|
20
22
|
const parser_1 = require("@babel/parser");
|
|
21
23
|
const traverse_1 = __importDefault(require("@babel/traverse"));
|
|
@@ -57,9 +59,18 @@ function fixMisplacedLineComments(code) {
|
|
|
57
59
|
function outputObjectMethods(outPath, methods) {
|
|
58
60
|
let dist = methods
|
|
59
61
|
.map((m) => {
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
//
|
|
62
|
+
var _a;
|
|
63
|
+
// 区分处理 leadingComments:
|
|
64
|
+
// - CommentBlock(/** ... */ JSDoc):属于方法自身的文档注释,encode 扁平化时应删除。
|
|
65
|
+
// - CommentLine(// ...):Babel 会把上一属性/方法的行尾注释错挂到当前方法的
|
|
66
|
+
// leadingComments,删除会导致行尾注释丢失,因此保留,交由 fixMisplacedLineComments
|
|
67
|
+
// 在生成后还原到上一属性行尾。
|
|
68
|
+
if ((_a = m.leadingComments) === null || _a === void 0 ? void 0 : _a.length) {
|
|
69
|
+
m.leadingComments = m.leadingComments.filter((c) => c.type === "CommentLine");
|
|
70
|
+
if (!m.leadingComments.length) {
|
|
71
|
+
m.leadingComments = undefined;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
63
74
|
return m;
|
|
64
75
|
})
|
|
65
76
|
.map((m) => (0, generator_1.default)(m))
|
|
@@ -390,8 +401,12 @@ function importMods(outDir, srcAst, mapContext, useAlias = false) {
|
|
|
390
401
|
/**
|
|
391
402
|
* 去除第三方模块方法
|
|
392
403
|
* @param srcAst - 源码AST
|
|
404
|
+
* @param mapContext
|
|
405
|
+
* @param skipMethodKeys - 需要跳过(不删除、不还原)的方法完整名集合,
|
|
406
|
+
* 元素形如 `method__mod`。用于稽核出方法体不一致的工具方法:
|
|
407
|
+
* 保留其内联定义与 this.method__mod 调用,交由用户人工处理。
|
|
393
408
|
*/
|
|
394
|
-
function deleteModMethods(srcAst, mapContext) {
|
|
409
|
+
function deleteModMethods(srcAst, mapContext, skipMethodKeys = new Set()) {
|
|
395
410
|
const deps = new Set();
|
|
396
411
|
mapContext
|
|
397
412
|
.getModNames()
|
|
@@ -408,6 +423,10 @@ function deleteModMethods(srcAst, mapContext) {
|
|
|
408
423
|
return;
|
|
409
424
|
}
|
|
410
425
|
const depName = split.length ? split[split.length - 1] : "";
|
|
426
|
+
// 稽核不一致的方法:保留内联定义,不删除
|
|
427
|
+
if (skipMethodKeys.has(methodName)) {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
411
430
|
deps.has(depName) && path.remove();
|
|
412
431
|
}
|
|
413
432
|
});
|
|
@@ -423,6 +442,10 @@ function deleteModMethods(srcAst, mapContext) {
|
|
|
423
442
|
}
|
|
424
443
|
const depName = split[split.length - 1];
|
|
425
444
|
if (deps.has(depName)) {
|
|
445
|
+
// 稽核不一致的方法:保留 this.method__mod 调用形态,不还原
|
|
446
|
+
if (skipMethodKeys.has(methodName)) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
426
449
|
path.node.property.name = methodName.replace(`__${depName}`, "");
|
|
427
450
|
if (core_1.types.isThisExpression(path.node.object)) {
|
|
428
451
|
path.node.object = core_1.types.identifier(depName);
|
|
@@ -509,3 +532,217 @@ function getUnknownDepNames(srcAst, knownDeps) {
|
|
|
509
532
|
});
|
|
510
533
|
return [...depNames];
|
|
511
534
|
}
|
|
535
|
+
/**
|
|
536
|
+
* 递归清除节点及其子树上所有 comments 字段(leading/inner/trailing),
|
|
537
|
+
* 用于“忽略注释”的代码比对。
|
|
538
|
+
*/
|
|
539
|
+
function stripCommentsDeep(node) {
|
|
540
|
+
if (!node || typeof node !== "object") {
|
|
541
|
+
return;
|
|
542
|
+
}
|
|
543
|
+
// 处理数组节点
|
|
544
|
+
if (Array.isArray(node)) {
|
|
545
|
+
node.forEach((child) => stripCommentsDeep(child));
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
const n = node;
|
|
549
|
+
n.leadingComments = null;
|
|
550
|
+
n.innerComments = null;
|
|
551
|
+
n.trailingComments = null;
|
|
552
|
+
for (const key of Object.keys(n)) {
|
|
553
|
+
const val = n[key];
|
|
554
|
+
if (val && typeof val === "object") {
|
|
555
|
+
stripCommentsDeep(val);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
/**
|
|
560
|
+
* 生成节点的归一化代码:去注释 + generate + 压缩空白/换行,
|
|
561
|
+
* 使仅注释或排版差异的代码视为相同。
|
|
562
|
+
*/
|
|
563
|
+
function normalizeNodeCode(node) {
|
|
564
|
+
// 克隆避免污染原 AST
|
|
565
|
+
const cloned = JSON.parse(JSON.stringify(node));
|
|
566
|
+
stripCommentsDeep(cloned);
|
|
567
|
+
const { code } = (0, generator_1.default)(cloned, { compact: true });
|
|
568
|
+
// 压缩连续空白与换行,统一比较基准
|
|
569
|
+
return code.replace(/\s+/g, " ").trim();
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* 对单个克隆的方法节点,在其方法体内应用与 encode `modifyObjectMethods`
|
|
573
|
+
* (非 root 分支)一致的调用重命名,使其与 coded.js 内联方法体可比对:
|
|
574
|
+
* - `this.method`(method 属于该 mod 自身方法集)→ `this.method__entry`
|
|
575
|
+
* - `dep.method`(dep 属于 mod.dependencies 且 method 在 dep.methods)→ `this.method__dep`
|
|
576
|
+
* 同时把方法 key 由 `method` 重命名为 `method__entry`。
|
|
577
|
+
*
|
|
578
|
+
* 注意:仅作用于传入的方法节点子树,不污染原 AST(调用方需传入克隆节点)。
|
|
579
|
+
*/
|
|
580
|
+
function renameCallsInsideMethod(methodNode, entry, selfMethods, deps) {
|
|
581
|
+
// 重命名方法 key:method → method__entry
|
|
582
|
+
if (core_1.types.isIdentifier(methodNode.key)) {
|
|
583
|
+
methodNode.key.name = `${methodNode.key.name}__${entry}`;
|
|
584
|
+
}
|
|
585
|
+
// 在方法体内遍历 MemberExpression 做调用重命名
|
|
586
|
+
// 使用 path 作用域限定在该方法节点内
|
|
587
|
+
const selfSet = new Set(selfMethods);
|
|
588
|
+
const depMap = new Map();
|
|
589
|
+
deps.forEach((d) => depMap.set(d.name, new Set(d.methods)));
|
|
590
|
+
// 手动遍历方法体(避免引入完整 traverse 的作用域问题)
|
|
591
|
+
const visit = (node) => {
|
|
592
|
+
if (!node || typeof node !== "object") {
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (Array.isArray(node)) {
|
|
596
|
+
node.forEach((child) => visit(child));
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (core_1.types.isMemberExpression(node)) {
|
|
600
|
+
// this.method → this.method__entry(method 属于自身方法集)
|
|
601
|
+
if (core_1.types.isThisExpression(node.object) &&
|
|
602
|
+
core_1.types.isIdentifier(node.property) &&
|
|
603
|
+
selfSet.has(node.property.name)) {
|
|
604
|
+
node.property.name = `${node.property.name}__${entry}`;
|
|
605
|
+
}
|
|
606
|
+
// dep.method → this.method__dep
|
|
607
|
+
if (core_1.types.isIdentifier(node.object) &&
|
|
608
|
+
core_1.types.isIdentifier(node.property) &&
|
|
609
|
+
depMap.has(node.object.name) &&
|
|
610
|
+
depMap.get(node.object.name).has(node.property.name)) {
|
|
611
|
+
const depName = node.object.name;
|
|
612
|
+
node.object = core_1.types.thisExpression();
|
|
613
|
+
node.property.name = `${node.property.name}__${depName}`;
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
for (const key of Object.keys(node)) {
|
|
617
|
+
if (key === "loc" || key === "start" || key === "end" || key === "range") {
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
const val = node[key];
|
|
621
|
+
if (val && typeof val === "object") {
|
|
622
|
+
visit(val);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
visit(methodNode);
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* 稽核 coded.js 中内联工具方法的方法体是否与源文件一致(忽略注释)。
|
|
630
|
+
*
|
|
631
|
+
* 比对基准:对源文件中对应方法应用与 encode 一致的重命名后,生成归一化代码,
|
|
632
|
+
* 与 coded.js 内联方法体的归一化代码比对。若不一致,说明 coded.js 被手动修改
|
|
633
|
+
* 或源文件已漂移,应跳过该方法的 decode(保留内联定义与 this.method__mod 调用)。
|
|
634
|
+
*
|
|
635
|
+
* @param srcAst - coded.js 的 AST(已包裹为 mount 对象)
|
|
636
|
+
* @param mapContext - mod.map 上下文
|
|
637
|
+
* @returns 不一致的方法列表
|
|
638
|
+
*/
|
|
639
|
+
function auditModMethods(srcAst, mapContext) {
|
|
640
|
+
const mismatches = [];
|
|
641
|
+
// 所有已知依赖名并集(与 deleteModMethods 一致)
|
|
642
|
+
const deps = new Set();
|
|
643
|
+
mapContext
|
|
644
|
+
.getModNames()
|
|
645
|
+
.flatMap((name) => { var _a; return Object.keys((_a = mapContext.getMod(name)) === null || _a === void 0 ? void 0 : _a.dependencies); })
|
|
646
|
+
.forEach((dep) => deps.add(dep));
|
|
647
|
+
// 缓存源文件 AST,避免重复解析
|
|
648
|
+
const srcAstCache = new Map();
|
|
649
|
+
const getSrcAst = (modName) => {
|
|
650
|
+
if (srcAstCache.has(modName)) {
|
|
651
|
+
return srcAstCache.get(modName);
|
|
652
|
+
}
|
|
653
|
+
const srcPath = mapContext.getAbsoluteSrcPathByMod(modName);
|
|
654
|
+
const ast = srcPath ? parseSrcAst(srcPath) : undefined;
|
|
655
|
+
srcAstCache.set(modName, ast);
|
|
656
|
+
return ast;
|
|
657
|
+
};
|
|
658
|
+
(0, traverse_1.default)(srcAst, {
|
|
659
|
+
ObjectMethod(path) {
|
|
660
|
+
if (!core_1.types.isIdentifier(path.node.key)) {
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
const fullMethodName = path.node.key.name;
|
|
664
|
+
const split = fullMethodName.split("__");
|
|
665
|
+
if (split.length < 2) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
const depName = split[split.length - 1];
|
|
669
|
+
if (!deps.has(depName)) {
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
// 原始方法名:去掉 __depName 后缀
|
|
673
|
+
const originalName = fullMethodName.slice(0, fullMethodName.length - depName.length - 2);
|
|
674
|
+
const srcPath = mapContext.getAbsoluteSrcPathByMod(depName);
|
|
675
|
+
const depSrcAst = getSrcAst(depName);
|
|
676
|
+
if (!depSrcAst || !srcPath) {
|
|
677
|
+
// 源文件不可读,无法稽核,跳过(不视为不一致)
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
// 从源文件取出对应方法节点
|
|
681
|
+
const srcMethods = getObjectMethodsByEntryAndMethodNames(depSrcAst, depName, [originalName]);
|
|
682
|
+
if (!srcMethods.length) {
|
|
683
|
+
// 源文件中找不到该方法,视为不一致(源文件已变更)
|
|
684
|
+
mismatches.push({
|
|
685
|
+
methodKey: fullMethodName,
|
|
686
|
+
methodName: originalName,
|
|
687
|
+
modName: depName,
|
|
688
|
+
srcPath
|
|
689
|
+
});
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
// 克隆源方法节点,应用 encode 重命名,归一化
|
|
693
|
+
// 自身方法集取源文件中该 entry 对象的所有方法名(与 encode modifyObjectMethods 一致),
|
|
694
|
+
// 而非 mod.map 的引用集(补充依赖场景下引用集可能为空)
|
|
695
|
+
const allSrcMethods = getObjectMethodsByEntryAndMethodNames(depSrcAst, depName);
|
|
696
|
+
const selfMethodNames = getObjectMethodNames(allSrcMethods);
|
|
697
|
+
const srcCloned = JSON.parse(JSON.stringify(srcMethods[0]));
|
|
698
|
+
const mod = mapContext.getMod(depName);
|
|
699
|
+
const depEntries = mod
|
|
700
|
+
? Object.keys(mod.dependencies).map((name) => ({
|
|
701
|
+
name,
|
|
702
|
+
methods: mod.dependencies[name].methods
|
|
703
|
+
}))
|
|
704
|
+
: [];
|
|
705
|
+
renameCallsInsideMethod(srcCloned, depName, selfMethodNames, depEntries);
|
|
706
|
+
const srcNormalized = normalizeNodeCode(srcCloned);
|
|
707
|
+
// coded.js 内联方法节点归一化(直接比对,已是重命名后形态)
|
|
708
|
+
const codedCloned = JSON.parse(JSON.stringify(path.node));
|
|
709
|
+
const codedNormalized = normalizeNodeCode(codedCloned);
|
|
710
|
+
if (srcNormalized !== codedNormalized) {
|
|
711
|
+
mismatches.push({
|
|
712
|
+
methodKey: fullMethodName,
|
|
713
|
+
methodName: originalName,
|
|
714
|
+
modName: depName,
|
|
715
|
+
srcPath
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
return mismatches;
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* 检测入口对象内是否含有 `__mod` 后缀的方法定义。
|
|
724
|
+
*
|
|
725
|
+
* 用于 encode 防护:当源文件(index.js)的 mount 对象内残留带 `__mod` 后缀的方法时,
|
|
726
|
+
* 说明用户直接复用了 decode 产物(含未还原的内联工具方法)作为二开源文件。
|
|
727
|
+
* 此类源文件不应再通过 require 引入工具方法做依赖编码,否则会导致方法重复定义、
|
|
728
|
+
* mod.map 错乱。encode 检测到此场景应禁止 require 引入,只做无依赖编码。
|
|
729
|
+
*
|
|
730
|
+
* @param srcAst - 源码AST
|
|
731
|
+
* @param entry - 入口对象名
|
|
732
|
+
* @returns 含 `__mod` 后缀的方法名列表(如 `["validate__util", "fetchData__util"]`)
|
|
733
|
+
*/
|
|
734
|
+
function getInlinedModMethodNames(srcAst, entry) {
|
|
735
|
+
const inlinedNames = [];
|
|
736
|
+
// 取入口对象的所有方法,筛选出 key 含 __ 后缀的
|
|
737
|
+
const methods = getObjectMethodsByEntryAndMethodNames(srcAst, entry);
|
|
738
|
+
methods.forEach((method) => {
|
|
739
|
+
if (!core_1.types.isIdentifier(method.key)) {
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
const name = method.key.name;
|
|
743
|
+
if (name.split("__").length >= 2) {
|
|
744
|
+
inlinedNames.push(name);
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
return inlinedNames;
|
|
748
|
+
}
|