@motifx/compiler 1.1.0 → 1.2.0-rc.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 +2 -2
- package/bin/motif-lint.cjs +4 -2
- package/dist/index.cjs +720 -49
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.esm.js +720 -49
- package/dist/index.esm.js.map +1 -1
- package/package.json +2 -2
package/dist/index.esm.js
CHANGED
|
@@ -11,6 +11,7 @@ import htmlTags from 'html-tags';
|
|
|
11
11
|
import svgTags from 'svg-tags';
|
|
12
12
|
import * as generatorNs from '@babel/generator';
|
|
13
13
|
import * as path from 'node:path';
|
|
14
|
+
import { promises } from 'node:fs';
|
|
14
15
|
import * as ts from 'typescript';
|
|
15
16
|
import pPath from 'path';
|
|
16
17
|
|
|
@@ -483,12 +484,6 @@ const makeHtmlAttr = (prop, name, value, path) => {
|
|
|
483
484
|
return t.objectProperty(t.stringLiteral(name), value || t.booleanLiteral(true));
|
|
484
485
|
}
|
|
485
486
|
else if (t.isArrowFunctionExpression(value) && value !== null) {
|
|
486
|
-
if (t.isBlockStatement(value.body)) {
|
|
487
|
-
t.functionExpression(null, value.params, value.body);
|
|
488
|
-
}
|
|
489
|
-
else {
|
|
490
|
-
t.functionExpression(null, value.params, t.blockStatement([t.returnStatement(value.body)]));
|
|
491
|
-
}
|
|
492
487
|
return t.objectProperty(t.stringLiteral(name), value);
|
|
493
488
|
}
|
|
494
489
|
else if (t.isFunctionExpression(value)) {
|
|
@@ -713,33 +708,9 @@ const mergeHtmlAttributes = (htmlAttrlist) => {
|
|
|
713
708
|
};
|
|
714
709
|
const makeHtmlEvents = (prop, name, value, path) => {
|
|
715
710
|
if (!t.isStringLiteral(value) && !t.isBooleanLiteral(value) && value !== null) {
|
|
716
|
-
if (t.
|
|
717
|
-
var el;
|
|
718
|
-
if (t.isBlockStatement(value.body)) {
|
|
719
|
-
el = t.arrowFunctionExpression(value.params, value.body);
|
|
720
|
-
}
|
|
721
|
-
else {
|
|
722
|
-
el = t.arrowFunctionExpression(value.params, t.blockStatement([t.returnStatement(value.body)]));
|
|
723
|
-
}
|
|
724
|
-
var evName = name.toLowerCase().replace("on", '').replace('doubleclick', 'dblclick');
|
|
725
|
-
return t.callExpression(t.identifier("sender.motif.on"), [t.stringLiteral(evName), el]);
|
|
726
|
-
}
|
|
727
|
-
else if (t.isFunctionExpression(value)) {
|
|
728
|
-
var el;
|
|
729
|
-
if (t.isBlockStatement(value.body)) {
|
|
730
|
-
el = t.functionExpression(null, value.params, value.body);
|
|
731
|
-
}
|
|
732
|
-
else {
|
|
733
|
-
el = t.functionExpression(null, value.params, t.blockStatement([t.returnStatement(value.body)]));
|
|
734
|
-
}
|
|
711
|
+
if (t.isExpression(value)) {
|
|
735
712
|
var evName = name.toLowerCase().replace("on", '').replace('doubleclick', 'dblclick');
|
|
736
|
-
return t.callExpression(t.identifier("sender.motif.on"), [t.stringLiteral(evName),
|
|
737
|
-
}
|
|
738
|
-
else {
|
|
739
|
-
if (t.isExpression(value)) {
|
|
740
|
-
var evName = name.toLowerCase().replace("on", '').replace('doubleclick', 'dblclick');
|
|
741
|
-
return t.callExpression(t.identifier("sender.motif.on"), [t.stringLiteral(evName), value]);
|
|
742
|
-
}
|
|
713
|
+
return t.callExpression(t.identifier("sender.motif.on"), [t.stringLiteral(evName), value]);
|
|
743
714
|
}
|
|
744
715
|
}
|
|
745
716
|
else {
|
|
@@ -796,7 +767,10 @@ const ParentIfStatement = (path, state) => {
|
|
|
796
767
|
else ;
|
|
797
768
|
return state;
|
|
798
769
|
};
|
|
799
|
-
const returnIfStatement = (value, enableDefaultreturn = true, defaultLiteral = t.booleanLiteral(false)) => {
|
|
770
|
+
const returnIfStatement = (value, enableDefaultreturn = true, defaultLiteral = t.booleanLiteral(false), callFunctions = false) => {
|
|
771
|
+
if (callFunctions && t.isFunctionExpression(value)) {
|
|
772
|
+
return value;
|
|
773
|
+
}
|
|
800
774
|
var ifStatement = findIfStatements(value);
|
|
801
775
|
var mx = t.binaryExpression('===', t.unaryExpression('typeof', value), t.stringLiteral('function'));
|
|
802
776
|
var rtrn;
|
|
@@ -810,13 +784,15 @@ const returnIfStatement = (value, enableDefaultreturn = true, defaultLiteral = t
|
|
|
810
784
|
}
|
|
811
785
|
}
|
|
812
786
|
else if (t.isMemberExpression(value) || t.isIdentifier(value)) {
|
|
813
|
-
rtrn = t.returnStatement(
|
|
787
|
+
rtrn = t.returnStatement(callFunctions
|
|
788
|
+
? t.conditionalExpression(t.binaryExpression('===', t.unaryExpression('typeof', value), t.stringLiteral('function')), t.callExpression(value, []), value)
|
|
789
|
+
: value);
|
|
814
790
|
var rtrns = t.ifStatement(ifStatement, t.blockStatement([rtrn]));
|
|
815
791
|
if (enableDefaultreturn) {
|
|
816
|
-
return t.arrowFunctionExpression([], t.blockStatement([rtrns, t.returnStatement(defaultLiteral)]));
|
|
792
|
+
return cacheGuardCalls(t.arrowFunctionExpression([], t.blockStatement([rtrns, t.returnStatement(defaultLiteral)])), [value]);
|
|
817
793
|
}
|
|
818
794
|
else {
|
|
819
|
-
return t.arrowFunctionExpression([], t.blockStatement([rtrns]));
|
|
795
|
+
return cacheGuardCalls(t.arrowFunctionExpression([], t.blockStatement([rtrns])), [value]);
|
|
820
796
|
}
|
|
821
797
|
}
|
|
822
798
|
else if (t.isFunctionExpression(value)) {
|
|
@@ -835,20 +811,20 @@ const returnIfStatement = (value, enableDefaultreturn = true, defaultLiteral = t
|
|
|
835
811
|
var le = t.logicalExpression('&&', ifStatementA, ifStatementB);
|
|
836
812
|
rtrn = t.returnStatement(value);
|
|
837
813
|
if (enableDefaultreturn) {
|
|
838
|
-
return t.arrowFunctionExpression([], t.blockStatement([t.ifStatement(le, t.blockStatement([rtrn])), t.returnStatement(defaultLiteral)]));
|
|
814
|
+
return cacheGuardCalls(t.arrowFunctionExpression([], t.blockStatement([t.ifStatement(le, t.blockStatement([rtrn])), t.returnStatement(defaultLiteral)])), [value.left, value.right]);
|
|
839
815
|
}
|
|
840
816
|
else {
|
|
841
|
-
return t.arrowFunctionExpression([], t.blockStatement([t.ifStatement(le, t.blockStatement([rtrn]))]));
|
|
817
|
+
return cacheGuardCalls(t.arrowFunctionExpression([], t.blockStatement([t.ifStatement(le, t.blockStatement([rtrn]))])), [value.left, value.right]);
|
|
842
818
|
}
|
|
843
819
|
}
|
|
844
820
|
else if (t.isCallExpression(value)) {
|
|
845
821
|
rtrn = t.returnStatement(t.conditionalExpression(mx, t.callExpression(value, []), value));
|
|
846
822
|
var rtrns = t.ifStatement(ifStatement, t.blockStatement([rtrn]));
|
|
847
823
|
if (enableDefaultreturn) {
|
|
848
|
-
return t.arrowFunctionExpression([], t.blockStatement([rtrns, t.returnStatement(defaultLiteral)]));
|
|
824
|
+
return cacheGuardCalls(t.arrowFunctionExpression([], t.blockStatement([rtrns, t.returnStatement(defaultLiteral)])), [value]);
|
|
849
825
|
}
|
|
850
826
|
else {
|
|
851
|
-
return t.arrowFunctionExpression([], t.blockStatement([rtrns]));
|
|
827
|
+
return cacheGuardCalls(t.arrowFunctionExpression([], t.blockStatement([rtrns])), [value]);
|
|
852
828
|
}
|
|
853
829
|
}
|
|
854
830
|
else {
|
|
@@ -918,6 +894,95 @@ const findIfStatements = (value) => {
|
|
|
918
894
|
}
|
|
919
895
|
return ifStatement;
|
|
920
896
|
};
|
|
897
|
+
const isEffectNode = (node) => t.isCallExpression(node) || t.isOptionalCallExpression(node) || t.isNewExpression(node) ||
|
|
898
|
+
t.isTaggedTemplateExpression(node) || t.isUpdateExpression(node) || t.isAssignmentExpression(node) ||
|
|
899
|
+
t.isAwaitExpression(node) || t.isYieldExpression(node);
|
|
900
|
+
const hasEffect = (node) => {
|
|
901
|
+
if (!node || t.isFunction(node) || t.isClass(node))
|
|
902
|
+
return false;
|
|
903
|
+
if (isEffectNode(node))
|
|
904
|
+
return true;
|
|
905
|
+
for (const key of t.VISITOR_KEYS[node.type] ?? []) {
|
|
906
|
+
const child = node[key];
|
|
907
|
+
if (Array.isArray(child) ? child.some(c => hasEffect(c)) : hasEffect(child))
|
|
908
|
+
return true;
|
|
909
|
+
}
|
|
910
|
+
return false;
|
|
911
|
+
};
|
|
912
|
+
const guardCallLinks = (root, found) => {
|
|
913
|
+
if (!t.isExpression(root) || t.isFunction(root))
|
|
914
|
+
return;
|
|
915
|
+
if (!t.isIdentifier(root) && !t.isMemberExpression(root) && !t.isThisExpression(root) && !t.isLiteral(root) && hasEffect(root)) {
|
|
916
|
+
found.add(root);
|
|
917
|
+
}
|
|
918
|
+
if (t.isMemberExpression(root))
|
|
919
|
+
guardCallLinks(root.object, found);
|
|
920
|
+
else if (t.isCallExpression(root) && t.isExpression(root.callee))
|
|
921
|
+
guardCallLinks(root.callee, found);
|
|
922
|
+
};
|
|
923
|
+
const collectNames = (node, names) => {
|
|
924
|
+
if (!node)
|
|
925
|
+
return;
|
|
926
|
+
if (t.isIdentifier(node))
|
|
927
|
+
names.add(node.name);
|
|
928
|
+
for (const key of t.VISITOR_KEYS[node.type] ?? []) {
|
|
929
|
+
const child = node[key];
|
|
930
|
+
if (Array.isArray(child))
|
|
931
|
+
child.forEach(c => collectNames(c, names));
|
|
932
|
+
else
|
|
933
|
+
collectNames(child, names);
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
const cacheGuardCalls = (fn, roots) => {
|
|
937
|
+
if (!t.isBlockStatement(fn.body))
|
|
938
|
+
return fn;
|
|
939
|
+
const links = new Set();
|
|
940
|
+
roots.forEach(r => guardCallLinks(r, links));
|
|
941
|
+
if (links.size === 0)
|
|
942
|
+
return fn;
|
|
943
|
+
const used = new Set();
|
|
944
|
+
collectNames(fn, used);
|
|
945
|
+
const assigned = new Map();
|
|
946
|
+
let counter = 0;
|
|
947
|
+
const nextName = () => {
|
|
948
|
+
let name;
|
|
949
|
+
do {
|
|
950
|
+
name = `__r${++counter}`;
|
|
951
|
+
} while (used.has(name));
|
|
952
|
+
return name;
|
|
953
|
+
};
|
|
954
|
+
const rewrite = (node) => {
|
|
955
|
+
if (!node || typeof node !== 'object' || !node.type)
|
|
956
|
+
return node;
|
|
957
|
+
if (links.has(node)) {
|
|
958
|
+
const known = assigned.get(node);
|
|
959
|
+
if (known)
|
|
960
|
+
return t.identifier(known);
|
|
961
|
+
const name = nextName();
|
|
962
|
+
assigned.set(node, name);
|
|
963
|
+
return t.assignmentExpression('=', t.identifier(name), rewriteChildren(node));
|
|
964
|
+
}
|
|
965
|
+
if (t.isFunction(node) || t.isClass(node))
|
|
966
|
+
return node;
|
|
967
|
+
return rewriteChildren(node);
|
|
968
|
+
};
|
|
969
|
+
const rewriteChildren = (node) => {
|
|
970
|
+
const copy = t.cloneNode(node, false);
|
|
971
|
+
for (const key of t.VISITOR_KEYS[node.type] ?? []) {
|
|
972
|
+
const child = node[key];
|
|
973
|
+
copy[key] = Array.isArray(child) ? child.map(rewrite) : rewrite(child);
|
|
974
|
+
}
|
|
975
|
+
return copy;
|
|
976
|
+
};
|
|
977
|
+
const body = rewriteChildren(fn.body);
|
|
978
|
+
if (assigned.size === 0)
|
|
979
|
+
return fn;
|
|
980
|
+
const declaration = t.variableDeclaration('let', [...assigned.values()].map(name => t.variableDeclarator(t.identifier(name))));
|
|
981
|
+
body.body.unshift(declaration);
|
|
982
|
+
const result = t.cloneNode(fn, false);
|
|
983
|
+
result.body = body;
|
|
984
|
+
return result;
|
|
985
|
+
};
|
|
921
986
|
const makeDirectives = (prop, name, value, path, state) => {
|
|
922
987
|
var result = null;
|
|
923
988
|
if (value != null) {
|
|
@@ -956,7 +1021,7 @@ const makeDirectives = (prop, name, value, path, state) => {
|
|
|
956
1021
|
}
|
|
957
1022
|
else {
|
|
958
1023
|
if (t.isExpression(value)) {
|
|
959
|
-
result = t.expressionStatement(t.callExpression(t.identifier(callerName), [returnIfStatement(value, false)]));
|
|
1024
|
+
result = t.expressionStatement(t.callExpression(t.identifier(callerName), [returnIfStatement(value, false, t.booleanLiteral(false), true)]));
|
|
960
1025
|
}
|
|
961
1026
|
}
|
|
962
1027
|
}
|
|
@@ -1038,7 +1103,7 @@ const makePreDirectives = (prop, name, value, path) => {
|
|
|
1038
1103
|
fail('MJX012', `Directive "${name}" does not accept an object literal.`, prop);
|
|
1039
1104
|
}
|
|
1040
1105
|
else if (t.isExpression(value)) {
|
|
1041
|
-
result = t.expressionStatement(t.callExpression(t.identifier(callerName), [returnIfStatement(value, true, t.booleanLiteral(true))]));
|
|
1106
|
+
result = t.expressionStatement(t.callExpression(t.identifier(callerName), [returnIfStatement(value, true, t.booleanLiteral(true), true)]));
|
|
1042
1107
|
}
|
|
1043
1108
|
}
|
|
1044
1109
|
else {
|
|
@@ -1579,7 +1644,7 @@ const getProps = (props, path, state) => {
|
|
|
1579
1644
|
refEntries.push({ prop, lowered: t.arrowFunctionExpression([t.identifier('sender')], t.blockStatement(body)) });
|
|
1580
1645
|
}
|
|
1581
1646
|
else if (t.isFunctionExpression(value) || t.isArrowFunctionExpression(value)) {
|
|
1582
|
-
refEntries.push({ prop, lowered: t.arrowFunctionExpression(value.params, value.body) });
|
|
1647
|
+
refEntries.push({ prop, lowered: t.arrowFunctionExpression(value.params, value.body, value.async) });
|
|
1583
1648
|
}
|
|
1584
1649
|
else if (t.isExpression(value)) {
|
|
1585
1650
|
var cx = t.expressionStatement(value);
|
|
@@ -2212,7 +2277,7 @@ const mkText = (exp) => {
|
|
|
2212
2277
|
var mx = t.binaryExpression('===', t.unaryExpression('typeof', exp), t.stringLiteral('function'));
|
|
2213
2278
|
var rtrn = t.returnStatement(t.conditionalExpression(mx, t.callExpression(exp, []), t.arrowFunctionExpression([], exp)));
|
|
2214
2279
|
var rtrns = t.ifStatement(ifStatement, t.blockStatement([rtrn]));
|
|
2215
|
-
var kx = t.arrowFunctionExpression([], t.blockStatement([rtrns]));
|
|
2280
|
+
var kx = cacheGuardCalls(t.arrowFunctionExpression([], t.blockStatement([rtrns])), [exp]);
|
|
2216
2281
|
a1 = t.callExpression(id('sender.bindings.add'), [t.stringLiteral("textContent"), kx]);
|
|
2217
2282
|
}
|
|
2218
2283
|
}
|
|
@@ -2278,7 +2343,7 @@ const mkText = (exp) => {
|
|
|
2278
2343
|
// var a1 = t.callExpression(id('sender.bind.logic'), [kx, exp.right]);
|
|
2279
2344
|
var rtrns = t.ifStatement(ifStatement, t.blockStatement([t.returnStatement(exp.left)]));
|
|
2280
2345
|
var tryStatement = t.tryStatement(t.blockStatement([rtrns]), t.catchClause(null, t.blockStatement([t.returnStatement(t.nullLiteral())])), null);
|
|
2281
|
-
var kx = t.arrowFunctionExpression([], t.blockStatement([tryStatement]));
|
|
2346
|
+
var kx = cacheGuardCalls(t.arrowFunctionExpression([], t.blockStatement([tryStatement])), [exp.left]);
|
|
2282
2347
|
if (!t.isFunctionDeclaration(exp.right) && !t.isArrowFunctionExpression(exp.right)) {
|
|
2283
2348
|
t.ifStatement(t.identifier('__v'), t.blockStatement([t.returnStatement(exp.right)]), t.blockStatement([t.returnStatement(t.callExpression(t.identifier(motifComponent()), [t.objectExpression([])]))]));
|
|
2284
2349
|
exp.right = t.arrowFunctionExpression([t.identifier("__v")], t.blockStatement([t.returnStatement(exp.right)]));
|
|
@@ -2298,7 +2363,7 @@ const mkText = (exp) => {
|
|
|
2298
2363
|
var mx = t.binaryExpression('===', t.unaryExpression('typeof', exp), t.stringLiteral('function'));
|
|
2299
2364
|
var rtrn = t.returnStatement(t.conditionalExpression(mx, t.callExpression(exp, []), t.arrowFunctionExpression([], exp)));
|
|
2300
2365
|
var rtrns = t.ifStatement(ifStatement, t.blockStatement([rtrn]));
|
|
2301
|
-
var kx = t.arrowFunctionExpression([], t.blockStatement([rtrns]));
|
|
2366
|
+
var kx = cacheGuardCalls(t.arrowFunctionExpression([], t.blockStatement([rtrns])), [exp]);
|
|
2302
2367
|
a1 = t.callExpression(id('sender.bindings.add'), [t.stringLiteral("textContent"), kx]);
|
|
2303
2368
|
}
|
|
2304
2369
|
else {
|
|
@@ -2306,7 +2371,7 @@ const mkText = (exp) => {
|
|
|
2306
2371
|
var mx = t.binaryExpression('===', t.unaryExpression('typeof', exp), t.stringLiteral('function'));
|
|
2307
2372
|
var rtrn = t.returnStatement(t.conditionalExpression(mx, t.callExpression(exp, []), t.arrowFunctionExpression([], exp)));
|
|
2308
2373
|
var rtrns = t.ifStatement(ifStatement, t.blockStatement([rtrn]));
|
|
2309
|
-
var kx = t.arrowFunctionExpression([], t.blockStatement([rtrns]));
|
|
2374
|
+
var kx = cacheGuardCalls(t.arrowFunctionExpression([], t.blockStatement([rtrns])), [exp]);
|
|
2310
2375
|
a1 = t.callExpression(id('sender.bindings.add'), [t.stringLiteral("textContent"), kx]);
|
|
2311
2376
|
}
|
|
2312
2377
|
var arrayBody = t.arrowFunctionExpression([id('sender')], t.blockStatement([t.expressionStatement(a1)]));
|
|
@@ -2472,7 +2537,7 @@ function ParseComponent(path, state) {
|
|
|
2472
2537
|
// var rtrn = t.returnStatement(t.conditionalExpression(mx, t.callExpression(cx.left, []), t.arrowFunctionExpression([], cx.left)));
|
|
2473
2538
|
var rtrns = t.ifStatement(ifStatement, t.blockStatement([t.returnStatement(cx.left)]));
|
|
2474
2539
|
var tryStatement = t.tryStatement(t.blockStatement([rtrns]), t.catchClause(null, t.blockStatement([t.returnStatement(t.nullLiteral())])), null);
|
|
2475
|
-
var kx = t.arrowFunctionExpression([], t.blockStatement([tryStatement]));
|
|
2540
|
+
var kx = cacheGuardCalls(t.arrowFunctionExpression([], t.blockStatement([tryStatement])), [cx.left]);
|
|
2476
2541
|
if (!t.isFunctionDeclaration(cx.right) && !t.isArrowFunctionExpression(cx.right)) {
|
|
2477
2542
|
t.ifStatement(t.identifier('__v'), t.blockStatement([t.returnStatement(cx.right)]), t.blockStatement([t.returnStatement(t.callExpression(t.identifier(motifComponent()), [t.objectExpression([])]))]));
|
|
2478
2543
|
cx.right = t.arrowFunctionExpression([t.identifier("__v")], t.blockStatement([t.returnStatement(cx.right)]));
|
|
@@ -2617,7 +2682,7 @@ function ParseFrament(path, state) {
|
|
|
2617
2682
|
else {
|
|
2618
2683
|
var rtrnsPlain = t.ifStatement(ifStatement, t.blockStatement([t.returnStatement(exp.left)]));
|
|
2619
2684
|
var tryStatement = t.tryStatement(t.blockStatement([rtrnsPlain]), t.catchClause(null, t.blockStatement([t.returnStatement(t.nullLiteral())])), null);
|
|
2620
|
-
condFn = t.arrowFunctionExpression([], t.blockStatement([tryStatement]));
|
|
2685
|
+
condFn = cacheGuardCalls(t.arrowFunctionExpression([], t.blockStatement([tryStatement])), [exp.left]);
|
|
2621
2686
|
}
|
|
2622
2687
|
if (!t.isFunctionDeclaration(exp.right) && !t.isArrowFunctionExpression(exp.right)) {
|
|
2623
2688
|
exp.right = t.arrowFunctionExpression([t.identifier("val")], t.blockStatement([t.returnStatement(exp.right)]));
|
|
@@ -3126,11 +3191,492 @@ class Compiler {
|
|
|
3126
3191
|
}
|
|
3127
3192
|
}
|
|
3128
3193
|
|
|
3194
|
+
const SUPER_METHODS = ['build', 'setState', 'reState', 'setText', 'style', 'dispose', 'disposeAsync', 'using', 'doWork', 'getService', '$', 'useModel'];
|
|
3195
|
+
const SUPER_ACCESSORS = ['context', 'siblings', 'serviceProvider', 'isWait'];
|
|
3196
|
+
const CORE_COMPONENT_CLASSES = ['Component', 'ComponentBase', 'ContentBlock', 'ContentBody', 'FragmentNode', 'Frame', 'RouterLink', 'RouterView', 'Transport', 'TransportTo', 'Virtualization'];
|
|
3197
|
+
const coreComponents = new Set(CORE_COMPONENT_CLASSES);
|
|
3198
|
+
const superNames = new Set([...SUPER_METHODS, ...SUPER_ACCESSORS]);
|
|
3199
|
+
const quickMember = new RegExp(`(?:^|[\\s;{}])(?:async\\s+|get\\s+|set\\s+|override\\s+|public\\s+|protected\\s+)*(?:${[...superNames].map(n => n.replace('$', '\\$')).join('|')})\\s*\\(`, 'm');
|
|
3200
|
+
function mayHaveSuperOverrides(code) {
|
|
3201
|
+
return /\bextends\b/.test(code) && quickMember.test(code);
|
|
3202
|
+
}
|
|
3203
|
+
function isCoreSource(source) {
|
|
3204
|
+
return source === '@motifx/core' || source.startsWith('@motifx/core/');
|
|
3205
|
+
}
|
|
3206
|
+
function isCoreComponent(name) {
|
|
3207
|
+
return coreComponents.has(name);
|
|
3208
|
+
}
|
|
3209
|
+
function parseModule(code, filename) {
|
|
3210
|
+
const isTs = /\.[mc]?tsx?$/.test(filename);
|
|
3211
|
+
const isTsx = /\.tsx$/.test(filename);
|
|
3212
|
+
const plugins = isTs ? ['typescript', 'decorators-legacy', 'classProperties'] : ['jsx', 'decorators-legacy', 'classProperties'];
|
|
3213
|
+
if (isTsx)
|
|
3214
|
+
plugins.push('jsx');
|
|
3215
|
+
try {
|
|
3216
|
+
const ast = babel.parseSync(code, {
|
|
3217
|
+
filename,
|
|
3218
|
+
babelrc: false,
|
|
3219
|
+
configFile: false,
|
|
3220
|
+
browserslistConfigFile: false,
|
|
3221
|
+
sourceType: 'unambiguous',
|
|
3222
|
+
parserOpts: { plugins, errorRecovery: true },
|
|
3223
|
+
});
|
|
3224
|
+
return ast ?? null;
|
|
3225
|
+
}
|
|
3226
|
+
catch {
|
|
3227
|
+
return null;
|
|
3228
|
+
}
|
|
3229
|
+
}
|
|
3230
|
+
function isSuperMember(node, name) {
|
|
3231
|
+
if (!node || !(t.isMemberExpression(node) || t.isOptionalMemberExpression(node)) || !t.isSuper(node.object))
|
|
3232
|
+
return false;
|
|
3233
|
+
if (!node.computed && t.isIdentifier(node.property))
|
|
3234
|
+
return node.property.name === name;
|
|
3235
|
+
return t.isStringLiteral(node.property) && node.property.value === name;
|
|
3236
|
+
}
|
|
3237
|
+
function directUse(node, name, use) {
|
|
3238
|
+
if (use === 'call' && (t.isCallExpression(node) || t.isOptionalCallExpression(node))) {
|
|
3239
|
+
const callee = node.callee;
|
|
3240
|
+
if (isSuperMember(callee, name))
|
|
3241
|
+
return true;
|
|
3242
|
+
if ((t.isMemberExpression(callee) || t.isOptionalMemberExpression(callee)) && isSuperMember(callee.object, name)
|
|
3243
|
+
&& t.isIdentifier(callee.property) && (callee.property.name === 'call' || callee.property.name === 'apply'))
|
|
3244
|
+
return true;
|
|
3245
|
+
}
|
|
3246
|
+
if (use === 'get' && isSuperMember(node, name))
|
|
3247
|
+
return true;
|
|
3248
|
+
if (use === 'set' && t.isAssignmentExpression(node) && isSuperMember(node.left, name))
|
|
3249
|
+
return true;
|
|
3250
|
+
return false;
|
|
3251
|
+
}
|
|
3252
|
+
function definitely(node, name, use) {
|
|
3253
|
+
if (!node || typeof node !== 'object')
|
|
3254
|
+
return false;
|
|
3255
|
+
if (t.isFunction(node) || t.isClass(node))
|
|
3256
|
+
return false;
|
|
3257
|
+
if (directUse(node, name, use))
|
|
3258
|
+
return true;
|
|
3259
|
+
if (use === 'get' && t.isAssignmentExpression(node) && isSuperMember(node.left, name))
|
|
3260
|
+
return definitely(node.right, name, use);
|
|
3261
|
+
if (t.isLogicalExpression(node))
|
|
3262
|
+
return definitely(node.left, name, use);
|
|
3263
|
+
if (t.isConditionalExpression(node))
|
|
3264
|
+
return definitely(node.test, name, use) || (definitely(node.consequent, name, use) && definitely(node.alternate, name, use));
|
|
3265
|
+
if (t.isOptionalCallExpression(node) || t.isOptionalMemberExpression(node)) {
|
|
3266
|
+
const head = t.isOptionalCallExpression(node) ? node.callee : node.object;
|
|
3267
|
+
return definitely(head, name, use);
|
|
3268
|
+
}
|
|
3269
|
+
for (const key of t.VISITOR_KEYS[node.type] ?? []) {
|
|
3270
|
+
const child = node[key];
|
|
3271
|
+
if (Array.isArray(child) ? child.some(c => definitely(c, name, use)) : definitely(child, name, use))
|
|
3272
|
+
return true;
|
|
3273
|
+
}
|
|
3274
|
+
return false;
|
|
3275
|
+
}
|
|
3276
|
+
function mentions(node, name, use) {
|
|
3277
|
+
if (!node || typeof node !== 'object')
|
|
3278
|
+
return false;
|
|
3279
|
+
if (directUse(node, name, use))
|
|
3280
|
+
return true;
|
|
3281
|
+
if (t.isClass(node))
|
|
3282
|
+
return false;
|
|
3283
|
+
for (const key of t.VISITOR_KEYS[node.type] ?? []) {
|
|
3284
|
+
const child = node[key];
|
|
3285
|
+
if (Array.isArray(child) ? child.some(c => mentions(c, name, use)) : mentions(child, name, use))
|
|
3286
|
+
return true;
|
|
3287
|
+
}
|
|
3288
|
+
return false;
|
|
3289
|
+
}
|
|
3290
|
+
const join = (a, b) => a === null ? b : b === null ? a : a && b;
|
|
3291
|
+
const GUARDS = {
|
|
3292
|
+
build: ['isBuilt', 'isDisposed', 'isWait'],
|
|
3293
|
+
dispose: ['isDisposed'],
|
|
3294
|
+
disposeAsync: ['isDisposed'],
|
|
3295
|
+
};
|
|
3296
|
+
function isGuardTest(test, member) {
|
|
3297
|
+
const allowed = GUARDS[member];
|
|
3298
|
+
if (!allowed)
|
|
3299
|
+
return false;
|
|
3300
|
+
if (t.isLogicalExpression(test) && test.operator === '||')
|
|
3301
|
+
return isGuardTest(test.left, member) && isGuardTest(test.right, member);
|
|
3302
|
+
return t.isMemberExpression(test) && t.isThisExpression(test.object) && !test.computed && t.isIdentifier(test.property) && allowed.includes(test.property.name);
|
|
3303
|
+
}
|
|
3304
|
+
function isBareReturn(node) {
|
|
3305
|
+
if (t.isReturnStatement(node))
|
|
3306
|
+
return true;
|
|
3307
|
+
return t.isBlockStatement(node) && node.body.length === 1 && t.isReturnStatement(node.body[0]);
|
|
3308
|
+
}
|
|
3309
|
+
function flow(node, called, name, use, member) {
|
|
3310
|
+
const plain = (c) => ({ normal: c, returns: true, breaks: null, continues: null });
|
|
3311
|
+
if (!node)
|
|
3312
|
+
return plain(called);
|
|
3313
|
+
const d = (e) => called || definitely(e, name, use);
|
|
3314
|
+
if (t.isBlockStatement(node) || t.isProgram(node))
|
|
3315
|
+
return block(node.body, called, name, use, member);
|
|
3316
|
+
if (t.isExpressionStatement(node))
|
|
3317
|
+
return plain(d(node.expression));
|
|
3318
|
+
if (t.isVariableDeclaration(node))
|
|
3319
|
+
return plain(called || node.declarations.some(x => definitely(x.init, name, use)));
|
|
3320
|
+
if (t.isReturnStatement(node))
|
|
3321
|
+
return { normal: null, returns: d(node.argument), breaks: null, continues: null };
|
|
3322
|
+
if (t.isThrowStatement(node))
|
|
3323
|
+
return { normal: null, returns: true, breaks: null, continues: null };
|
|
3324
|
+
if (t.isBreakStatement(node))
|
|
3325
|
+
return { normal: null, returns: true, breaks: called, continues: null };
|
|
3326
|
+
if (t.isContinueStatement(node))
|
|
3327
|
+
return { normal: null, returns: true, breaks: null, continues: called };
|
|
3328
|
+
if (t.isIfStatement(node)) {
|
|
3329
|
+
const tc = d(node.test);
|
|
3330
|
+
if (!tc && isGuardTest(node.test, member) && isBareReturn(node.consequent)) {
|
|
3331
|
+
const alt = node.alternate ? flow(node.alternate, tc, name, use, member) : plain(tc);
|
|
3332
|
+
return { normal: alt.normal, returns: alt.returns, breaks: alt.breaks, continues: alt.continues };
|
|
3333
|
+
}
|
|
3334
|
+
const cons = flow(node.consequent, tc, name, use, member);
|
|
3335
|
+
const alt = node.alternate ? flow(node.alternate, tc, name, use, member) : plain(tc);
|
|
3336
|
+
return { normal: join(cons.normal, alt.normal), returns: cons.returns && alt.returns, breaks: join(cons.breaks, alt.breaks), continues: join(cons.continues, alt.continues) };
|
|
3337
|
+
}
|
|
3338
|
+
if (t.isWhileStatement(node) || t.isForStatement(node) || t.isForInStatement(node) || t.isForOfStatement(node)) {
|
|
3339
|
+
let tc = called;
|
|
3340
|
+
if (t.isForStatement(node))
|
|
3341
|
+
tc = called || definitely(node.init, name, use) || definitely(node.test, name, use);
|
|
3342
|
+
else if (t.isWhileStatement(node))
|
|
3343
|
+
tc = d(node.test);
|
|
3344
|
+
else
|
|
3345
|
+
tc = d(node.right);
|
|
3346
|
+
const body = flow(node.body, tc, name, use, member);
|
|
3347
|
+
return { normal: join(tc, body.breaks), returns: body.returns, breaks: null, continues: null };
|
|
3348
|
+
}
|
|
3349
|
+
if (t.isDoWhileStatement(node)) {
|
|
3350
|
+
const body = flow(node.body, called, name, use, member);
|
|
3351
|
+
const afterBody = join(body.normal, body.continues);
|
|
3352
|
+
const afterTest = afterBody === null ? null : afterBody || definitely(node.test, name, use);
|
|
3353
|
+
return { normal: join(afterTest, body.breaks), returns: body.returns, breaks: null, continues: null };
|
|
3354
|
+
}
|
|
3355
|
+
if (t.isSwitchStatement(node)) {
|
|
3356
|
+
const dc = d(node.discriminant);
|
|
3357
|
+
let fall = null;
|
|
3358
|
+
let returns = true;
|
|
3359
|
+
let breaks = null;
|
|
3360
|
+
let continues = null;
|
|
3361
|
+
let hasDefault = false;
|
|
3362
|
+
for (const c of node.cases) {
|
|
3363
|
+
if (!c.test)
|
|
3364
|
+
hasDefault = true;
|
|
3365
|
+
const entry = join(fall, dc || definitely(c.test, name, use));
|
|
3366
|
+
const out = entry === null ? null : block(c.consequent, entry, name, use, member);
|
|
3367
|
+
if (out) {
|
|
3368
|
+
returns = returns && out.returns;
|
|
3369
|
+
breaks = join(breaks, out.breaks);
|
|
3370
|
+
continues = join(continues, out.continues);
|
|
3371
|
+
fall = out.normal;
|
|
3372
|
+
}
|
|
3373
|
+
else {
|
|
3374
|
+
fall = null;
|
|
3375
|
+
}
|
|
3376
|
+
}
|
|
3377
|
+
let normal = join(fall, breaks);
|
|
3378
|
+
if (!hasDefault)
|
|
3379
|
+
normal = join(normal, dc);
|
|
3380
|
+
return { normal, returns, breaks: null, continues };
|
|
3381
|
+
}
|
|
3382
|
+
if (t.isTryStatement(node)) {
|
|
3383
|
+
const tryOut = flow(node.block, called, name, use, member);
|
|
3384
|
+
const catchOut = node.handler ? flow(node.handler.body, called, name, use, member) : null;
|
|
3385
|
+
let normal = catchOut ? join(tryOut.normal, catchOut.normal) : tryOut.normal;
|
|
3386
|
+
let returns = tryOut.returns && (catchOut ? catchOut.returns : true);
|
|
3387
|
+
let breaks = catchOut ? join(tryOut.breaks, catchOut.breaks) : tryOut.breaks;
|
|
3388
|
+
let continues = catchOut ? join(tryOut.continues, catchOut.continues) : tryOut.continues;
|
|
3389
|
+
if (node.finalizer) {
|
|
3390
|
+
const fin = flow(node.finalizer, false, name, use, member);
|
|
3391
|
+
if (fin.normal === true) {
|
|
3392
|
+
normal = normal === null ? null : true;
|
|
3393
|
+
returns = true;
|
|
3394
|
+
breaks = breaks === null ? null : true;
|
|
3395
|
+
continues = continues === null ? null : true;
|
|
3396
|
+
}
|
|
3397
|
+
else if (fin.normal === null) {
|
|
3398
|
+
return { normal: null, returns: returns && fin.returns, breaks: fin.breaks, continues: fin.continues };
|
|
3399
|
+
}
|
|
3400
|
+
returns = returns && fin.returns;
|
|
3401
|
+
}
|
|
3402
|
+
return { normal, returns, breaks, continues };
|
|
3403
|
+
}
|
|
3404
|
+
if (t.isLabeledStatement(node)) {
|
|
3405
|
+
const body = flow(node.body, called, name, use, member);
|
|
3406
|
+
return { normal: join(body.normal, body.breaks), returns: body.returns, breaks: null, continues: body.continues };
|
|
3407
|
+
}
|
|
3408
|
+
if (t.isFunctionDeclaration(node) || t.isClassDeclaration(node))
|
|
3409
|
+
return plain(called);
|
|
3410
|
+
let c = called;
|
|
3411
|
+
for (const key of t.VISITOR_KEYS[node.type] ?? []) {
|
|
3412
|
+
const child = node[key];
|
|
3413
|
+
if (Array.isArray(child) ? child.some(x => definitely(x, name, use)) : definitely(child, name, use)) {
|
|
3414
|
+
c = true;
|
|
3415
|
+
break;
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
return plain(c);
|
|
3419
|
+
}
|
|
3420
|
+
function block(body, called, name, use, member) {
|
|
3421
|
+
let state = called;
|
|
3422
|
+
let returns = true;
|
|
3423
|
+
let breaks = null;
|
|
3424
|
+
let continues = null;
|
|
3425
|
+
for (const s of body) {
|
|
3426
|
+
if (state === null)
|
|
3427
|
+
break;
|
|
3428
|
+
const out = flow(s, state, name, use, member);
|
|
3429
|
+
returns = returns && out.returns;
|
|
3430
|
+
breaks = join(breaks, out.breaks);
|
|
3431
|
+
continues = join(continues, out.continues);
|
|
3432
|
+
state = out.normal;
|
|
3433
|
+
}
|
|
3434
|
+
return { normal: state, returns, breaks, continues };
|
|
3435
|
+
}
|
|
3436
|
+
function checkMemberBody(body, member, accessor) {
|
|
3437
|
+
const use = accessor === 'method' ? 'call' : accessor;
|
|
3438
|
+
if (!mentions(body, member, use))
|
|
3439
|
+
return 'never';
|
|
3440
|
+
const out = flow(body, false, member, use, member);
|
|
3441
|
+
return out.normal !== false && out.returns ? 'ok' : 'path';
|
|
3442
|
+
}
|
|
3443
|
+
function memberName(m) {
|
|
3444
|
+
const key = m.key;
|
|
3445
|
+
if (m.computed)
|
|
3446
|
+
return t.isStringLiteral(key) ? key.value : null;
|
|
3447
|
+
if (t.isIdentifier(key))
|
|
3448
|
+
return key.name;
|
|
3449
|
+
if (t.isStringLiteral(key))
|
|
3450
|
+
return key.value;
|
|
3451
|
+
return null;
|
|
3452
|
+
}
|
|
3453
|
+
function analyzeModule(ast) {
|
|
3454
|
+
const imports = new Map();
|
|
3455
|
+
const classes = new Map();
|
|
3456
|
+
const exportsMap = new Map();
|
|
3457
|
+
const starSources = [];
|
|
3458
|
+
const findings = [];
|
|
3459
|
+
const classNodes = [];
|
|
3460
|
+
const noteClass = (node, name) => {
|
|
3461
|
+
if (name)
|
|
3462
|
+
classes.set(name, node);
|
|
3463
|
+
classNodes.push({ node, name: name ?? 'An anonymous class' });
|
|
3464
|
+
};
|
|
3465
|
+
const walk = (node, parent) => {
|
|
3466
|
+
if (!node || typeof node !== 'object' || !node.type)
|
|
3467
|
+
return;
|
|
3468
|
+
if (t.isImportDeclaration(node)) {
|
|
3469
|
+
for (const s of node.specifiers) {
|
|
3470
|
+
if (t.isImportSpecifier(s))
|
|
3471
|
+
imports.set(s.local.name, { source: node.source.value, name: t.isIdentifier(s.imported) ? s.imported.name : s.imported.value });
|
|
3472
|
+
else if (t.isImportDefaultSpecifier(s))
|
|
3473
|
+
imports.set(s.local.name, { source: node.source.value, name: 'default' });
|
|
3474
|
+
else if (t.isImportNamespaceSpecifier(s))
|
|
3475
|
+
imports.set(s.local.name, { source: node.source.value, name: '*' });
|
|
3476
|
+
}
|
|
3477
|
+
}
|
|
3478
|
+
else if (t.isExportNamedDeclaration(node)) {
|
|
3479
|
+
if (node.source) {
|
|
3480
|
+
for (const s of node.specifiers) {
|
|
3481
|
+
if (t.isExportSpecifier(s)) {
|
|
3482
|
+
const exported = t.isIdentifier(s.exported) ? s.exported.name : s.exported.value;
|
|
3483
|
+
exportsMap.set(exported, { from: node.source.value, imported: s.local.name });
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
else {
|
|
3488
|
+
for (const s of node.specifiers) {
|
|
3489
|
+
if (t.isExportSpecifier(s)) {
|
|
3490
|
+
const exported = t.isIdentifier(s.exported) ? s.exported.name : s.exported.value;
|
|
3491
|
+
exportsMap.set(exported, { local: s.local.name });
|
|
3492
|
+
}
|
|
3493
|
+
}
|
|
3494
|
+
const decl = node.declaration;
|
|
3495
|
+
if (t.isClassDeclaration(decl) && decl.id)
|
|
3496
|
+
exportsMap.set(decl.id.name, { local: decl.id.name });
|
|
3497
|
+
if (t.isVariableDeclaration(decl)) {
|
|
3498
|
+
for (const v of decl.declarations)
|
|
3499
|
+
if (t.isIdentifier(v.id))
|
|
3500
|
+
exportsMap.set(v.id.name, { local: v.id.name });
|
|
3501
|
+
}
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
else if (t.isExportDefaultDeclaration(node)) {
|
|
3505
|
+
const decl = node.declaration;
|
|
3506
|
+
if (t.isClassDeclaration(decl)) {
|
|
3507
|
+
const local = decl.id?.name ?? '*default*';
|
|
3508
|
+
if (!decl.id)
|
|
3509
|
+
classes.set(local, decl);
|
|
3510
|
+
exportsMap.set('default', { local });
|
|
3511
|
+
}
|
|
3512
|
+
else if (t.isIdentifier(decl)) {
|
|
3513
|
+
exportsMap.set('default', { local: decl.name });
|
|
3514
|
+
}
|
|
3515
|
+
}
|
|
3516
|
+
else if (t.isExportAllDeclaration(node)) {
|
|
3517
|
+
starSources.push(node.source.value);
|
|
3518
|
+
}
|
|
3519
|
+
if (t.isClassDeclaration(node)) {
|
|
3520
|
+
noteClass(node, node.id?.name ?? (t.isExportDefaultDeclaration(parent) ? '*default*' : null));
|
|
3521
|
+
}
|
|
3522
|
+
else if (t.isClassExpression(node)) {
|
|
3523
|
+
const name = node.id?.name ?? (t.isVariableDeclarator(parent) && t.isIdentifier(parent.id) ? parent.id.name : null);
|
|
3524
|
+
noteClass(node, name);
|
|
3525
|
+
}
|
|
3526
|
+
for (const key of t.VISITOR_KEYS[node.type] ?? []) {
|
|
3527
|
+
const child = node[key];
|
|
3528
|
+
if (Array.isArray(child))
|
|
3529
|
+
child.forEach(c => walk(c, node));
|
|
3530
|
+
else
|
|
3531
|
+
walk(child, node);
|
|
3532
|
+
}
|
|
3533
|
+
};
|
|
3534
|
+
walk(ast.program, null);
|
|
3535
|
+
const baseOf = (cls, seen) => {
|
|
3536
|
+
if (seen.has(cls))
|
|
3537
|
+
return { kind: 'unknown' };
|
|
3538
|
+
seen.add(cls);
|
|
3539
|
+
const sup = cls.superClass;
|
|
3540
|
+
if (t.isIdentifier(sup)) {
|
|
3541
|
+
const imp = imports.get(sup.name);
|
|
3542
|
+
if (imp && imp.name !== '*')
|
|
3543
|
+
return { kind: 'import', source: imp.source, name: imp.name };
|
|
3544
|
+
const local = classes.get(sup.name);
|
|
3545
|
+
if (local)
|
|
3546
|
+
return baseOf(local, seen);
|
|
3547
|
+
return { kind: 'unknown' };
|
|
3548
|
+
}
|
|
3549
|
+
if (t.isMemberExpression(sup) && t.isIdentifier(sup.object) && !sup.computed && t.isIdentifier(sup.property)) {
|
|
3550
|
+
const imp = imports.get(sup.object.name);
|
|
3551
|
+
if (imp && imp.name === '*')
|
|
3552
|
+
return { kind: 'import', source: imp.source, name: sup.property.name };
|
|
3553
|
+
}
|
|
3554
|
+
return { kind: 'unknown' };
|
|
3555
|
+
};
|
|
3556
|
+
for (const { node, name } of classNodes) {
|
|
3557
|
+
if (!node.superClass)
|
|
3558
|
+
continue;
|
|
3559
|
+
let base;
|
|
3560
|
+
for (const m of node.body.body) {
|
|
3561
|
+
if (!(t.isClassMethod(m) || t.isClassPrivateMethod(m)) || m.static || t.isClassPrivateMethod(m))
|
|
3562
|
+
continue;
|
|
3563
|
+
if (m.kind === 'constructor')
|
|
3564
|
+
continue;
|
|
3565
|
+
const member = memberName(m);
|
|
3566
|
+
if (!member || !superNames.has(member))
|
|
3567
|
+
continue;
|
|
3568
|
+
const accessor = m.kind === 'get' ? 'get' : m.kind === 'set' ? 'set' : 'method';
|
|
3569
|
+
if (accessor === 'method' && !SUPER_METHODS.includes(member))
|
|
3570
|
+
continue;
|
|
3571
|
+
if (accessor !== 'method' && !SUPER_ACCESSORS.includes(member))
|
|
3572
|
+
continue;
|
|
3573
|
+
const result = checkMemberBody(m.body, member, accessor);
|
|
3574
|
+
if (result === 'ok')
|
|
3575
|
+
continue;
|
|
3576
|
+
base ??= baseOf(node, new Set());
|
|
3577
|
+
findings.push({
|
|
3578
|
+
className: name === '*default*' ? 'The default-exported class' : name,
|
|
3579
|
+
member,
|
|
3580
|
+
accessor,
|
|
3581
|
+
kind: result,
|
|
3582
|
+
line: m.loc?.start.line ?? 0,
|
|
3583
|
+
column: m.loc?.start.column ?? 0,
|
|
3584
|
+
classLine: node.loc?.start.line ?? 0,
|
|
3585
|
+
base,
|
|
3586
|
+
});
|
|
3587
|
+
}
|
|
3588
|
+
}
|
|
3589
|
+
return {
|
|
3590
|
+
findings,
|
|
3591
|
+
classBase(exportedOrLocal) {
|
|
3592
|
+
const cls = classes.get(exportedOrLocal);
|
|
3593
|
+
return cls ? baseOf(cls, new Set()) : null;
|
|
3594
|
+
},
|
|
3595
|
+
exportOf(name) {
|
|
3596
|
+
return exportsMap.get(name) ?? null;
|
|
3597
|
+
},
|
|
3598
|
+
starSources,
|
|
3599
|
+
};
|
|
3600
|
+
}
|
|
3601
|
+
function superCallMessage(f) {
|
|
3602
|
+
const target = f.accessor === 'get' ? `read super.${f.member}` : f.accessor === 'set' ? `assign super.${f.member}` : `call super.${f.member}(…)`;
|
|
3603
|
+
return f.kind === 'never'
|
|
3604
|
+
? `${f.className}.${f.member} overrides a ComponentBase member and does not ${target}; MotifJS relies on it. Rename the member, or ${target} on every path.`
|
|
3605
|
+
: `${f.className}.${f.member} can finish without reaching super.${f.member} (a return, a branch, a loop or a catch skips it); MotifJS relies on it. ${target[0].toUpperCase() + target.slice(1)} on every path.`;
|
|
3606
|
+
}
|
|
3607
|
+
function createComponentResolver(resolve, read) {
|
|
3608
|
+
const cache = new Map();
|
|
3609
|
+
const shapes = new Map();
|
|
3610
|
+
const shapeOf = (file) => {
|
|
3611
|
+
let p = shapes.get(file);
|
|
3612
|
+
if (!p) {
|
|
3613
|
+
p = (async () => {
|
|
3614
|
+
const code = await read(file);
|
|
3615
|
+
if (code == null || code.length > 2_000_000)
|
|
3616
|
+
return null;
|
|
3617
|
+
const ast = parseModule(code, file);
|
|
3618
|
+
return ast ? analyzeModule(ast) : null;
|
|
3619
|
+
})();
|
|
3620
|
+
shapes.set(file, p);
|
|
3621
|
+
}
|
|
3622
|
+
return p;
|
|
3623
|
+
};
|
|
3624
|
+
const isComponentExport = (file, name, depth) => {
|
|
3625
|
+
const key = `${file}#${name}`;
|
|
3626
|
+
let p = cache.get(key);
|
|
3627
|
+
if (!p) {
|
|
3628
|
+
cache.set(key, Promise.resolve(false));
|
|
3629
|
+
p = (async () => {
|
|
3630
|
+
if (depth > 12)
|
|
3631
|
+
return false;
|
|
3632
|
+
const shape = await shapeOf(file);
|
|
3633
|
+
if (!shape)
|
|
3634
|
+
return false;
|
|
3635
|
+
const exp = shape.exportOf(name);
|
|
3636
|
+
if (exp?.from)
|
|
3637
|
+
return isComponentBase({ kind: 'import', source: exp.from, name: exp.imported ?? name }, file, depth + 1);
|
|
3638
|
+
const local = exp?.local ?? (name === 'default' ? '*default*' : null);
|
|
3639
|
+
if (local) {
|
|
3640
|
+
const base = shape.classBase(local);
|
|
3641
|
+
if (base)
|
|
3642
|
+
return isComponentBase(base, file, depth + 1);
|
|
3643
|
+
return false;
|
|
3644
|
+
}
|
|
3645
|
+
for (const star of shape.starSources) {
|
|
3646
|
+
if (await isComponentBase({ kind: 'import', source: star, name }, file, depth + 1))
|
|
3647
|
+
return true;
|
|
3648
|
+
}
|
|
3649
|
+
return false;
|
|
3650
|
+
})();
|
|
3651
|
+
cache.set(key, p);
|
|
3652
|
+
}
|
|
3653
|
+
return p;
|
|
3654
|
+
};
|
|
3655
|
+
const isComponentBase = async (base, importer, depth = 0) => {
|
|
3656
|
+
if (base.kind !== 'import')
|
|
3657
|
+
return false;
|
|
3658
|
+
if (isCoreSource(base.source))
|
|
3659
|
+
return isCoreComponent(base.name);
|
|
3660
|
+
let file = null;
|
|
3661
|
+
try {
|
|
3662
|
+
file = await resolve(base.source, importer);
|
|
3663
|
+
}
|
|
3664
|
+
catch {
|
|
3665
|
+
file = null;
|
|
3666
|
+
}
|
|
3667
|
+
if (!file)
|
|
3668
|
+
return false;
|
|
3669
|
+
return isComponentExport(file, base.name, depth);
|
|
3670
|
+
};
|
|
3671
|
+
return isComponentBase;
|
|
3672
|
+
}
|
|
3673
|
+
|
|
3129
3674
|
const fileRegex = /\.(jsx|tsx|aio|mjsx|mtsx)$/;
|
|
3130
3675
|
const decoratorFileRegex = /\.(ts|mts|cts|js|mjs|cjs)$/;
|
|
3131
3676
|
const declarationFileRegex = /\.d\.[mc]?ts$/;
|
|
3132
3677
|
const decoratorSyntaxRegex = /(?:^|[\s(,;{}])@[A-Za-z_$]/;
|
|
3133
3678
|
const assetQueryRegex = /(?:^|&)(?:raw|url|worker|sharedworker|inline)(?:[&=]|$)/;
|
|
3679
|
+
const analyzableFileRegex = /\.(jsx|tsx|mjsx|mtsx|ts|mts|cts|js|mjs|cjs)$/;
|
|
3134
3680
|
function sourceFile(id) {
|
|
3135
3681
|
if (id.charCodeAt(0) === 0)
|
|
3136
3682
|
return null;
|
|
@@ -3151,6 +3697,41 @@ function shouldLowerDecorators(id, src) {
|
|
|
3151
3697
|
return false;
|
|
3152
3698
|
return decoratorSyntaxRegex.test(src);
|
|
3153
3699
|
}
|
|
3700
|
+
function shouldCheckSuperCalls(id, src) {
|
|
3701
|
+
const file = sourceFile(id);
|
|
3702
|
+
if (file === null || !analyzableFileRegex.test(file) || declarationFileRegex.test(file))
|
|
3703
|
+
return false;
|
|
3704
|
+
if (file.replace(/\\/g, '/').includes('/node_modules/'))
|
|
3705
|
+
return false;
|
|
3706
|
+
return mayHaveSuperOverrides(src);
|
|
3707
|
+
}
|
|
3708
|
+
function lineFrame(src, line, column) {
|
|
3709
|
+
const lines = src.split(/\r?\n/);
|
|
3710
|
+
if (line < 1 || line > lines.length)
|
|
3711
|
+
return undefined;
|
|
3712
|
+
const from = Math.max(1, line - 1);
|
|
3713
|
+
const to = Math.min(lines.length, line + 1);
|
|
3714
|
+
const width = String(to).length;
|
|
3715
|
+
const out = [];
|
|
3716
|
+
for (let i = from; i <= to; i++) {
|
|
3717
|
+
out.push(`${i === line ? '>' : ' '} ${String(i).padStart(width)} | ${lines[i - 1]}`);
|
|
3718
|
+
if (i === line)
|
|
3719
|
+
out.push(` ${' '.repeat(width)} | ${' '.repeat(column)}^`);
|
|
3720
|
+
}
|
|
3721
|
+
return out.join('\n');
|
|
3722
|
+
}
|
|
3723
|
+
async function checkSuperCalls(src, file, isComponentBase) {
|
|
3724
|
+
const ast = parseModule(src, file);
|
|
3725
|
+
if (!ast)
|
|
3726
|
+
return [];
|
|
3727
|
+
const out = [];
|
|
3728
|
+
for (const f of analyzeModule(ast).findings) {
|
|
3729
|
+
if (!(await isComponentBase(f.base, file)))
|
|
3730
|
+
continue;
|
|
3731
|
+
out.push({ code: 'MJX015', message: superCallMessage(f), file, line: f.line, frame: lineFrame(src, f.line, f.column) });
|
|
3732
|
+
}
|
|
3733
|
+
return out;
|
|
3734
|
+
}
|
|
3154
3735
|
function legacyDecoratorsChecker() {
|
|
3155
3736
|
const byConfig = new Map();
|
|
3156
3737
|
return (file) => {
|
|
@@ -3178,10 +3759,45 @@ function explainMatches(opt, id) {
|
|
|
3178
3759
|
function vitePlugin(options = {}) {
|
|
3179
3760
|
const showDiagnostics = options.diagnostics !== false;
|
|
3180
3761
|
const usesLegacyDecorators = legacyDecoratorsChecker();
|
|
3762
|
+
let context = null;
|
|
3763
|
+
const makeResolver = () => createComponentResolver(async (source, importer) => {
|
|
3764
|
+
if (!context?.resolve)
|
|
3765
|
+
return null;
|
|
3766
|
+
const r = await context.resolve(source, importer, { skipSelf: true });
|
|
3767
|
+
if (!r || r.external)
|
|
3768
|
+
return null;
|
|
3769
|
+
return sourceFile(r.id);
|
|
3770
|
+
}, async (file) => {
|
|
3771
|
+
try {
|
|
3772
|
+
return await promises.readFile(file, 'utf8');
|
|
3773
|
+
}
|
|
3774
|
+
catch {
|
|
3775
|
+
return null;
|
|
3776
|
+
}
|
|
3777
|
+
});
|
|
3778
|
+
let isComponentBase = makeResolver();
|
|
3779
|
+
const pending = new Set();
|
|
3181
3780
|
return {
|
|
3182
3781
|
name: 'transform-file-motif-jsx',
|
|
3183
3782
|
enforce: 'pre',
|
|
3783
|
+
watchChange() {
|
|
3784
|
+
isComponentBase = makeResolver();
|
|
3785
|
+
},
|
|
3786
|
+
handleHotUpdate() {
|
|
3787
|
+
isComponentBase = makeResolver();
|
|
3788
|
+
},
|
|
3789
|
+
async buildEnd() {
|
|
3790
|
+
await Promise.all([...pending]);
|
|
3791
|
+
},
|
|
3184
3792
|
transform(src, id) {
|
|
3793
|
+
if (showDiagnostics && shouldCheckSuperCalls(id, src)) {
|
|
3794
|
+
context = this;
|
|
3795
|
+
const check = checkSuperCalls(src, sourceFile(id), isComponentBase)
|
|
3796
|
+
.then(found => { if (found.length > 0)
|
|
3797
|
+
printDiagnostics(found); }, () => { })
|
|
3798
|
+
.finally(() => { pending.delete(check); });
|
|
3799
|
+
pending.add(check);
|
|
3800
|
+
}
|
|
3185
3801
|
if (shouldLowerDecorators(id, src)) {
|
|
3186
3802
|
if (usesLegacyDecorators(sourceFile(id)))
|
|
3187
3803
|
return;
|
|
@@ -3284,6 +3900,7 @@ function lintProgram(program, files) {
|
|
|
3284
3900
|
continue;
|
|
3285
3901
|
if (wanted && !wanted.has(normalize(sf.fileName)))
|
|
3286
3902
|
continue;
|
|
3903
|
+
out.push(...superCallFindings(sf, checker));
|
|
3287
3904
|
if (!/\.(tsx|jsx|mtsx|mjsx)$/.test(sf.fileName))
|
|
3288
3905
|
continue;
|
|
3289
3906
|
const visit = (node) => {
|
|
@@ -3339,6 +3956,60 @@ function lintProgram(program, files) {
|
|
|
3339
3956
|
}
|
|
3340
3957
|
return out;
|
|
3341
3958
|
}
|
|
3959
|
+
const coreDeclarationFile = /[\\/](?:@motifx[\\/]core|motifjs[\\/](?:dist|src))[\\/]/;
|
|
3960
|
+
function derivesFromComponentBase(node, checker) {
|
|
3961
|
+
try {
|
|
3962
|
+
let type = checker.getTypeAtLocation(node);
|
|
3963
|
+
const seen = new Set();
|
|
3964
|
+
const queue = type ? [type] : [];
|
|
3965
|
+
while (queue.length > 0) {
|
|
3966
|
+
type = queue.shift();
|
|
3967
|
+
const target = (type.target ?? type);
|
|
3968
|
+
if (seen.has(target))
|
|
3969
|
+
continue;
|
|
3970
|
+
seen.add(target);
|
|
3971
|
+
const sym = target.getSymbol();
|
|
3972
|
+
if (sym && sym.getName() === 'ComponentBase' && sym.declarations?.some(d => coreDeclarationFile.test(d.getSourceFile().fileName)))
|
|
3973
|
+
return true;
|
|
3974
|
+
if (!(target.objectFlags & (ts.ObjectFlags.Class | ts.ObjectFlags.Interface)))
|
|
3975
|
+
continue;
|
|
3976
|
+
for (const b of checker.getBaseTypes(target) ?? [])
|
|
3977
|
+
queue.push(b);
|
|
3978
|
+
}
|
|
3979
|
+
}
|
|
3980
|
+
catch { }
|
|
3981
|
+
return false;
|
|
3982
|
+
}
|
|
3983
|
+
function superCallFindings(sf, checker) {
|
|
3984
|
+
if (!mayHaveSuperOverrides(sf.text))
|
|
3985
|
+
return [];
|
|
3986
|
+
const ast = parseModule(sf.text, sf.fileName);
|
|
3987
|
+
if (!ast)
|
|
3988
|
+
return [];
|
|
3989
|
+
const findings = analyzeModule(ast).findings;
|
|
3990
|
+
if (findings.length === 0)
|
|
3991
|
+
return [];
|
|
3992
|
+
const membersByLine = new Map();
|
|
3993
|
+
const collect = (n) => {
|
|
3994
|
+
if ((ts.isMethodDeclaration(n) || ts.isGetAccessorDeclaration(n) || ts.isSetAccessorDeclaration(n)) && (ts.isClassDeclaration(n.parent) || ts.isClassExpression(n.parent))) {
|
|
3995
|
+
const name = n.name && (ts.isIdentifier(n.name) || ts.isStringLiteral(n.name)) ? n.name.text : '';
|
|
3996
|
+
const lines = [n.getStart(sf), n.name.getStart(sf)].map(p => sf.getLineAndCharacterOfPosition(p).line + 1);
|
|
3997
|
+
for (const l of lines)
|
|
3998
|
+
membersByLine.set(`${l}:${name}`, n);
|
|
3999
|
+
}
|
|
4000
|
+
ts.forEachChild(n, collect);
|
|
4001
|
+
};
|
|
4002
|
+
collect(sf);
|
|
4003
|
+
const out = [];
|
|
4004
|
+
for (const f of findings) {
|
|
4005
|
+
const member = membersByLine.get(`${f.line}:${f.member}`);
|
|
4006
|
+
const cls = member?.parent;
|
|
4007
|
+
if (!member || !cls || !derivesFromComponentBase(cls, checker))
|
|
4008
|
+
continue;
|
|
4009
|
+
out.push({ code: 'MJX015', file: sf.fileName, line: f.line, message: superCallMessage(f), frame: codeFrame(sf, member) });
|
|
4010
|
+
}
|
|
4011
|
+
return out;
|
|
4012
|
+
}
|
|
3342
4013
|
/** tsconfig'den program kurup denetler. CLI bunu kullanır. */
|
|
3343
4014
|
function lintProject(options = {}) {
|
|
3344
4015
|
const projectPath = options.project ?? 'tsconfig.json';
|