@motifx/compiler 1.0.1 → 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/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.isArrowFunctionExpression(value)) {
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), el]);
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(value);
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)]));
@@ -3063,12 +3128,9 @@ class Compiler {
3063
3128
  },
3064
3129
  function devOnlyStrip() {
3065
3130
  let isDev = true;
3066
- try {
3067
- const flag = (0, eval)('typeof __MOTIF_DEV__ !== "undefined" ? __MOTIF_DEV__ : undefined');
3068
- if (typeof flag === 'boolean')
3069
- isDev = flag;
3070
- }
3071
- catch { /* ignore */ }
3131
+ const flag = globalThis.__MOTIF_DEV__;
3132
+ if (typeof flag === 'boolean')
3133
+ isDev = flag;
3072
3134
  if (typeof process !== 'undefined' && process?.env?.MOTIF_DEV === 'false')
3073
3135
  isDev = false;
3074
3136
  const consumed = new WeakSet();
@@ -3129,11 +3191,492 @@ class Compiler {
3129
3191
  }
3130
3192
  }
3131
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
+
3132
3674
  const fileRegex = /\.(jsx|tsx|aio|mjsx|mtsx)$/;
3133
3675
  const decoratorFileRegex = /\.(ts|mts|cts|js|mjs|cjs)$/;
3134
3676
  const declarationFileRegex = /\.d\.[mc]?ts$/;
3135
3677
  const decoratorSyntaxRegex = /(?:^|[\s(,;{}])@[A-Za-z_$]/;
3136
3678
  const assetQueryRegex = /(?:^|&)(?:raw|url|worker|sharedworker|inline)(?:[&=]|$)/;
3679
+ const analyzableFileRegex = /\.(jsx|tsx|mjsx|mtsx|ts|mts|cts|js|mjs|cjs)$/;
3137
3680
  function sourceFile(id) {
3138
3681
  if (id.charCodeAt(0) === 0)
3139
3682
  return null;
@@ -3154,6 +3697,41 @@ function shouldLowerDecorators(id, src) {
3154
3697
  return false;
3155
3698
  return decoratorSyntaxRegex.test(src);
3156
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
+ }
3157
3735
  function legacyDecoratorsChecker() {
3158
3736
  const byConfig = new Map();
3159
3737
  return (file) => {
@@ -3181,10 +3759,45 @@ function explainMatches(opt, id) {
3181
3759
  function vitePlugin(options = {}) {
3182
3760
  const showDiagnostics = options.diagnostics !== false;
3183
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();
3184
3780
  return {
3185
3781
  name: 'transform-file-motif-jsx',
3186
3782
  enforce: 'pre',
3783
+ watchChange() {
3784
+ isComponentBase = makeResolver();
3785
+ },
3786
+ handleHotUpdate() {
3787
+ isComponentBase = makeResolver();
3788
+ },
3789
+ async buildEnd() {
3790
+ await Promise.all([...pending]);
3791
+ },
3187
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
+ }
3188
3801
  if (shouldLowerDecorators(id, src)) {
3189
3802
  if (usesLegacyDecorators(sourceFile(id)))
3190
3803
  return;
@@ -3287,6 +3900,7 @@ function lintProgram(program, files) {
3287
3900
  continue;
3288
3901
  if (wanted && !wanted.has(normalize(sf.fileName)))
3289
3902
  continue;
3903
+ out.push(...superCallFindings(sf, checker));
3290
3904
  if (!/\.(tsx|jsx|mtsx|mjsx)$/.test(sf.fileName))
3291
3905
  continue;
3292
3906
  const visit = (node) => {
@@ -3342,6 +3956,60 @@ function lintProgram(program, files) {
3342
3956
  }
3343
3957
  return out;
3344
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
+ }
3345
4013
  /** tsconfig'den program kurup denetler. CLI bunu kullanır. */
3346
4014
  function lintProject(options = {}) {
3347
4015
  const projectPath = options.project ?? 'tsconfig.json';