@dereekb/dbx-web 13.11.14 → 13.11.16

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.
@@ -123,22 +123,20 @@
123
123
  * @returns The decorator name, or empty string when unrecognized.
124
124
  */ function getDecoratorName(decorator) {
125
125
  var expression = decorator === null || decorator === void 0 ? void 0 : decorator.expression;
126
- if (!expression) {
127
- return '';
128
- }
129
- if (expression.type === 'CallExpression') {
130
- var _expression_callee, _expression_callee1, _expression_callee_property;
131
- if (((_expression_callee = expression.callee) === null || _expression_callee === void 0 ? void 0 : _expression_callee.type) === 'Identifier') {
132
- return expression.callee.name;
133
- }
134
- if (((_expression_callee1 = expression.callee) === null || _expression_callee1 === void 0 ? void 0 : _expression_callee1.type) === 'MemberExpression' && ((_expression_callee_property = expression.callee.property) === null || _expression_callee_property === void 0 ? void 0 : _expression_callee_property.type) === 'Identifier') {
135
- return expression.callee.property.name;
126
+ var result = '';
127
+ if (expression) {
128
+ if (expression.type === 'CallExpression') {
129
+ var _expression_callee, _expression_callee1, _expression_callee_property;
130
+ if (((_expression_callee = expression.callee) === null || _expression_callee === void 0 ? void 0 : _expression_callee.type) === 'Identifier') {
131
+ result = expression.callee.name;
132
+ } else if (((_expression_callee1 = expression.callee) === null || _expression_callee1 === void 0 ? void 0 : _expression_callee1.type) === 'MemberExpression' && ((_expression_callee_property = expression.callee.property) === null || _expression_callee_property === void 0 ? void 0 : _expression_callee_property.type) === 'Identifier') {
133
+ result = expression.callee.property.name;
134
+ }
135
+ } else if (expression.type === 'Identifier') {
136
+ result = expression.name;
136
137
  }
137
138
  }
138
- if (expression.type === 'Identifier') {
139
- return expression.name;
140
- }
141
- return '';
139
+ return result;
142
140
  }
143
141
  /**
144
142
  * Returns the first decorator on the class that names a component-tier
@@ -192,16 +190,15 @@
192
190
  * @returns The property name, or null when not a simple key.
193
191
  */ function getClassMemberName(member) {
194
192
  var key = member === null || member === void 0 ? void 0 : member.key;
195
- if (!key || member.computed) {
196
- return null;
197
- }
198
- if (key.type === 'Identifier') {
199
- return key.name;
200
- }
201
- if (key.type === 'Literal' && typeof key.value === 'string') {
202
- return key.value;
193
+ var result = null;
194
+ if (key && !member.computed) {
195
+ if (key.type === 'Identifier') {
196
+ result = key.name;
197
+ } else if (key.type === 'Literal' && typeof key.value === 'string') {
198
+ result = key.value;
199
+ }
203
200
  }
204
- return null;
201
+ return result;
205
202
  }
206
203
  /**
207
204
  * Finds the `ngOnDestroy` method declaration on the given class, if any.
@@ -243,23 +240,23 @@
243
240
  * If the given expression is a `CallExpression` whose callee is one of the
244
241
  * accepted identifier names, returns the matching name. Otherwise null.
245
242
  *
243
+ * @param node - The expression AST node.
244
+ * @param names - The accepted identifier names.
245
+ * @returns The matched name, or null.
246
+ *
246
247
  * @example
247
248
  * ```
248
249
  * isCalledIdentifier(node, ['cleanSubscription', 'clean']) // returns 'cleanSubscription'
249
250
  * ```
250
- *
251
- * @param node - The expression AST node.
252
- * @param names - The accepted identifier names.
253
- * @returns The matched name, or null.
254
251
  */ function isCalledIdentifier(node, names) {
255
- if ((node === null || node === void 0 ? void 0 : node.type) !== 'CallExpression') {
256
- return null;
257
- }
258
- var callee = node.callee;
259
- if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'Identifier' && names.has(callee.name)) {
260
- return callee.name;
252
+ var result = null;
253
+ if ((node === null || node === void 0 ? void 0 : node.type) === 'CallExpression') {
254
+ var callee = node.callee;
255
+ if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'Identifier' && names.has(callee.name)) {
256
+ result = callee.name;
257
+ }
261
258
  }
262
- return null;
259
+ return result;
263
260
  }
264
261
  /**
265
262
  * Returns true when the given AST node is a `this.<propName>` MemberExpression.
@@ -283,27 +280,26 @@
283
280
  */ function ensureNamedImportFix(input) {
284
281
  var fixer = input.fixer, registry = input.registry, importName = input.importName, fromSource = input.fromSource;
285
282
  var existing = registry.bySource.get(fromSource);
286
- if (existing === null || existing === void 0 ? void 0 : existing.has(importName)) {
287
- return null;
288
- }
289
- var declaration = registry.sourceToDeclaration.get(fromSource);
290
283
  var result = null;
291
- if (declaration) {
292
- var _declaration_specifiers;
293
- var lastSpecifier = (_declaration_specifiers = declaration.specifiers) === null || _declaration_specifiers === void 0 ? void 0 : _declaration_specifiers[declaration.specifiers.length - 1];
294
- if (lastSpecifier) {
295
- var updatedSet = existing !== null && existing !== void 0 ? existing : new Set();
296
- updatedSet.add(importName);
297
- registry.bySource.set(fromSource, updatedSet);
284
+ if (!(existing === null || existing === void 0 ? void 0 : existing.has(importName))) {
285
+ var declaration = registry.sourceToDeclaration.get(fromSource);
286
+ if (declaration) {
287
+ var _declaration_specifiers;
288
+ var lastSpecifier = (_declaration_specifiers = declaration.specifiers) === null || _declaration_specifiers === void 0 ? void 0 : _declaration_specifiers[declaration.specifiers.length - 1];
289
+ if (lastSpecifier) {
290
+ var updatedSet = existing !== null && existing !== void 0 ? existing : new Set();
291
+ updatedSet.add(importName);
292
+ registry.bySource.set(fromSource, updatedSet);
293
+ registry.localToSource.set(importName, fromSource);
294
+ result = fixer.insertTextAfter(lastSpecifier, ", ".concat(importName));
295
+ }
296
+ } else if (registry.lastImportDeclaration) {
297
+ var updatedSet1 = existing !== null && existing !== void 0 ? existing : new Set();
298
+ updatedSet1.add(importName);
299
+ registry.bySource.set(fromSource, updatedSet1);
298
300
  registry.localToSource.set(importName, fromSource);
299
- result = fixer.insertTextAfter(lastSpecifier, ", ".concat(importName));
301
+ result = fixer.insertTextAfter(registry.lastImportDeclaration, "\nimport { ".concat(importName, " } from '").concat(fromSource, "';"));
300
302
  }
301
- } else if (registry.lastImportDeclaration) {
302
- var updatedSet1 = existing !== null && existing !== void 0 ? existing : new Set();
303
- updatedSet1.add(importName);
304
- registry.bySource.set(fromSource, updatedSet1);
305
- registry.localToSource.set(importName, fromSource);
306
- result = fixer.insertTextAfter(registry.lastImportDeclaration, "\nimport { ".concat(importName, " } from '").concat(fromSource, "';"));
307
303
  }
308
304
  return result;
309
305
  }
@@ -421,7 +417,7 @@
421
417
  * - Rewrites the initializer to `cleanSubscription(...)` (preserving any constructor argument).
422
418
  * - Inserts the `cleanSubscription` named import from `@dereekb/dbx-core` if missing.
423
419
  * - Removes any matching `this.<field>.destroy();` line from the same class's `ngOnDestroy`.
424
- */ var dbxWebRequireCleanSubscriptionRule = {
420
+ */ var DBX_WEB_REQUIRE_CLEAN_SUBSCRIPTION_RULE = {
425
421
  meta: {
426
422
  type: 'problem',
427
423
  fixable: 'code',
@@ -659,7 +655,7 @@
659
655
  * - Wraps the initializer with `completeOnDestroy(...)`.
660
656
  * - Inserts the `completeOnDestroy` named import from `@dereekb/dbx-core` if missing.
661
657
  * - Removes any matching `this.<field>.complete();` line from the same class's `ngOnDestroy`.
662
- */ var dbxWebRequireCompleteOnDestroyRule = {
658
+ */ var DBX_WEB_REQUIRE_COMPLETE_ON_DESTROY_RULE = {
663
659
  meta: {
664
660
  type: 'problem',
665
661
  fixable: 'code',
@@ -828,7 +824,7 @@
828
824
  * - When a class declares `implements OnDestroy` from `@angular/core` but has
829
825
  * no `ngOnDestroy()` method (e.g. left over from a previous run), the
830
826
  * orphaned implements clause is removed.
831
- */ var dbxWebNoRedundantOnDestroyRule = {
827
+ */ var DBX_WEB_NO_REDUNDANT_ON_DESTROY_RULE = {
832
828
  meta: {
833
829
  type: 'suggestion',
834
830
  fixable: 'code',
@@ -847,59 +843,56 @@
847
843
  create: function create(context) {
848
844
  var registry = createImportRegistry();
849
845
  var sourceCode = context.sourceCode;
850
- var visitClass = function visitClass(classNode) {
851
- var _ngOnDestroy_value_body, _ngOnDestroy_value;
852
- var matchedDecorator = findAngularComponentDecorator(classNode, registry);
853
- if (!matchedDecorator) {
854
- return;
855
- }
856
- var ngOnDestroy = findNgOnDestroyMethod(classNode);
857
- var body = ngOnDestroy === null || ngOnDestroy === void 0 ? void 0 : (_ngOnDestroy_value = ngOnDestroy.value) === null || _ngOnDestroy_value === void 0 ? void 0 : (_ngOnDestroy_value_body = _ngOnDestroy_value.body) === null || _ngOnDestroy_value_body === void 0 ? void 0 : _ngOnDestroy_value_body.body;
858
- if (!ngOnDestroy || !body) {
859
- var implementsMatch = findOnDestroyImplementsClause(classNode, registry);
860
- if (implementsMatch) {
861
- context.report({
862
- node: implementsMatch.clauseSpecifier,
863
- messageId: 'orphanedImplementsOnDestroy',
864
- fix: function fix(fixer) {
865
- return [
866
- fixer.removeRange(getImplementsSpecifierRemovalRange(implementsMatch, sourceCode))
867
- ];
868
- }
869
- });
870
- }
871
- return;
872
- }
873
- if (body.length === 0) {
846
+ var reportOrphanedImplements = function reportOrphanedImplements(classNode) {
847
+ var implementsMatch = findOnDestroyImplementsClause(classNode, registry);
848
+ if (implementsMatch) {
874
849
  context.report({
875
- node: ngOnDestroy,
876
- messageId: 'emptyNgOnDestroy',
850
+ node: implementsMatch.clauseSpecifier,
851
+ messageId: 'orphanedImplementsOnDestroy',
877
852
  fix: function fix(fixer) {
878
- return buildRemoveNgOnDestroyFixes({
879
- fixer: fixer,
880
- ngOnDestroy: ngOnDestroy,
881
- classNode: classNode,
882
- registry: registry,
883
- sourceCode: sourceCode
884
- });
853
+ return [
854
+ fixer.removeRange(getImplementsSpecifierRemovalRange(implementsMatch, sourceCode))
855
+ ];
885
856
  }
886
857
  });
887
- return;
888
858
  }
889
- var wrappedFields = collectWrappedFieldNames(classNode);
890
- var redundantStatements = [];
891
- var hasNonRedundantStatement = false;
859
+ };
860
+ var reportRemoveNgOnDestroy = function reportRemoveNgOnDestroy(ngOnDestroy, classNode, messageId) {
861
+ context.report({
862
+ node: ngOnDestroy,
863
+ messageId: messageId,
864
+ fix: function fix(fixer) {
865
+ return buildRemoveNgOnDestroyFixes({
866
+ fixer: fixer,
867
+ ngOnDestroy: ngOnDestroy,
868
+ classNode: classNode,
869
+ registry: registry,
870
+ sourceCode: sourceCode
871
+ });
872
+ }
873
+ });
874
+ };
875
+ var reportRedundantStatements = function reportRedundantStatements(entries) {
892
876
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
893
877
  try {
894
- for(var _iterator = body[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
895
- var statement = _step.value;
896
- var match = matchRedundantCleanupStatement(statement, wrappedFields);
897
- if (match) {
898
- redundantStatements.push(match);
899
- } else {
900
- hasNonRedundantStatement = true;
901
- }
902
- }
878
+ var _loop = function() {
879
+ var entry = _step.value;
880
+ context.report({
881
+ node: entry.statement,
882
+ messageId: 'redundantCleanupCall',
883
+ data: {
884
+ name: entry.fieldName,
885
+ method: entry.method,
886
+ wrapper: entry.wrapper
887
+ },
888
+ fix: function fix(fixer) {
889
+ return [
890
+ fixer.removeRange(getStatementRangeWithLeadingWhitespace(entry.statement, sourceCode))
891
+ ];
892
+ }
893
+ });
894
+ };
895
+ for(var _iterator = entries[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true)_loop();
903
896
  } catch (err) {
904
897
  _didIteratorError = true;
905
898
  _iteratorError = err;
@@ -914,58 +907,29 @@
914
907
  }
915
908
  }
916
909
  }
917
- if (redundantStatements.length === 0) {
918
- return;
910
+ };
911
+ var visitNgOnDestroyBody = function visitNgOnDestroyBody(ngOnDestroy, body, classNode) {
912
+ var _partitionNgOnDestroyStatements = partitionNgOnDestroyStatements(body, classNode), redundantStatements = _partitionNgOnDestroyStatements.redundantStatements, hasNonRedundantStatement = _partitionNgOnDestroyStatements.hasNonRedundantStatement;
913
+ if (redundantStatements.length > 0) {
914
+ if (hasNonRedundantStatement) {
915
+ reportRedundantStatements(redundantStatements);
916
+ } else {
917
+ reportRemoveNgOnDestroy(ngOnDestroy, classNode, 'redundantNgOnDestroy');
918
+ }
919
919
  }
920
- if (hasNonRedundantStatement) {
921
- var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
922
- try {
923
- var _loop = function() {
924
- var entry = _step1.value;
925
- context.report({
926
- node: entry.statement,
927
- messageId: 'redundantCleanupCall',
928
- data: {
929
- name: entry.fieldName,
930
- method: entry.method,
931
- wrapper: entry.wrapper
932
- },
933
- fix: function fix(fixer) {
934
- return [
935
- fixer.removeRange(getStatementRangeWithLeadingWhitespace(entry.statement, sourceCode))
936
- ];
937
- }
938
- });
939
- };
940
- for(var _iterator1 = redundantStatements[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true)_loop();
941
- } catch (err) {
942
- _didIteratorError1 = true;
943
- _iteratorError1 = err;
944
- } finally{
945
- try {
946
- if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
947
- _iterator1.return();
948
- }
949
- } finally{
950
- if (_didIteratorError1) {
951
- throw _iteratorError1;
952
- }
953
- }
920
+ };
921
+ var visitClass = function visitClass(classNode) {
922
+ if (findAngularComponentDecorator(classNode, registry)) {
923
+ var _ngOnDestroy_value_body, _ngOnDestroy_value;
924
+ var ngOnDestroy = findNgOnDestroyMethod(classNode);
925
+ var body = ngOnDestroy === null || ngOnDestroy === void 0 ? void 0 : (_ngOnDestroy_value = ngOnDestroy.value) === null || _ngOnDestroy_value === void 0 ? void 0 : (_ngOnDestroy_value_body = _ngOnDestroy_value.body) === null || _ngOnDestroy_value_body === void 0 ? void 0 : _ngOnDestroy_value_body.body;
926
+ if (!ngOnDestroy || !body) {
927
+ reportOrphanedImplements(classNode);
928
+ } else if (body.length === 0) {
929
+ reportRemoveNgOnDestroy(ngOnDestroy, classNode, 'emptyNgOnDestroy');
930
+ } else {
931
+ visitNgOnDestroyBody(ngOnDestroy, body, classNode);
954
932
  }
955
- } else {
956
- context.report({
957
- node: ngOnDestroy,
958
- messageId: 'redundantNgOnDestroy',
959
- fix: function fix(fixer) {
960
- return buildRemoveNgOnDestroyFixes({
961
- fixer: fixer,
962
- ngOnDestroy: ngOnDestroy,
963
- classNode: classNode,
964
- registry: registry,
965
- sourceCode: sourceCode
966
- });
967
- }
968
- });
969
933
  }
970
934
  };
971
935
  return {
@@ -1032,6 +996,87 @@
1032
996
  */ function wrapperNameFromInitializer(expression) {
1033
997
  return expression ? isCalledIdentifier(expression, HELPER_NAMES) : null;
1034
998
  }
999
+ /**
1000
+ * Splits an `ngOnDestroy` body into redundant cleanup matches and a flag
1001
+ * indicating whether any other (non-redundant) statement is present.
1002
+ *
1003
+ * @param body - The statements of the `ngOnDestroy` method body.
1004
+ * @param classNode - The owning class node, used to gather wrapped fields.
1005
+ * @returns The redundant matches and the non-redundant flag.
1006
+ */ function partitionNgOnDestroyStatements(body, classNode) {
1007
+ var wrappedFields = collectWrappedFieldNames(classNode);
1008
+ var redundantStatements = [];
1009
+ var hasNonRedundantStatement = false;
1010
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
1011
+ try {
1012
+ for(var _iterator = body[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
1013
+ var statement = _step.value;
1014
+ var match = matchRedundantCleanupStatement(statement, wrappedFields);
1015
+ if (match) {
1016
+ redundantStatements.push(match);
1017
+ } else {
1018
+ hasNonRedundantStatement = true;
1019
+ }
1020
+ }
1021
+ } catch (err) {
1022
+ _didIteratorError = true;
1023
+ _iteratorError = err;
1024
+ } finally{
1025
+ try {
1026
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
1027
+ _iterator.return();
1028
+ }
1029
+ } finally{
1030
+ if (_didIteratorError) {
1031
+ throw _iteratorError;
1032
+ }
1033
+ }
1034
+ }
1035
+ return {
1036
+ redundantStatements: redundantStatements,
1037
+ hasNonRedundantStatement: hasNonRedundantStatement
1038
+ };
1039
+ }
1040
+ /**
1041
+ * Returns the redundant method name (`destroy` / `complete`) when the call
1042
+ * expression is a zero-argument member call to one of those methods.
1043
+ *
1044
+ * @param expression - The expression to inspect.
1045
+ * @returns The method name and its callee MemberExpression, or null.
1046
+ */ function getRedundantMethodCall(expression) {
1047
+ var _expression_arguments, _expression_callee;
1048
+ var result = null;
1049
+ var isZeroArgMemberCall = (expression === null || expression === void 0 ? void 0 : expression.type) === 'CallExpression' && ((_expression_arguments = expression.arguments) === null || _expression_arguments === void 0 ? void 0 : _expression_arguments.length) === 0 && ((_expression_callee = expression.callee) === null || _expression_callee === void 0 ? void 0 : _expression_callee.type) === 'MemberExpression';
1050
+ if (isZeroArgMemberCall) {
1051
+ var _callee_property;
1052
+ var callee = expression.callee;
1053
+ var methodName = !callee.computed && ((_callee_property = callee.property) === null || _callee_property === void 0 ? void 0 : _callee_property.type) === 'Identifier' ? callee.property.name : null;
1054
+ if (methodName && REDUNDANT_METHODS.has(methodName)) {
1055
+ result = {
1056
+ methodName: methodName,
1057
+ callee: callee
1058
+ };
1059
+ }
1060
+ }
1061
+ return result;
1062
+ }
1063
+ /**
1064
+ * Returns the `this.<fieldName>` field name from a callee object, or null
1065
+ * when the receiver is not a non-computed `this.<identifier>` access.
1066
+ *
1067
+ * @param calleeObject - The callee's object (the receiver of the method call).
1068
+ * @returns The field name or null.
1069
+ */ function getThisFieldName(calleeObject) {
1070
+ var _calleeObject_property;
1071
+ var result = null;
1072
+ if ((calleeObject === null || calleeObject === void 0 ? void 0 : calleeObject.type) === 'MemberExpression' && !calleeObject.computed && ((_calleeObject_property = calleeObject.property) === null || _calleeObject_property === void 0 ? void 0 : _calleeObject_property.type) === 'Identifier') {
1073
+ var fieldName = calleeObject.property.name;
1074
+ if (isThisMemberAccess(calleeObject, fieldName)) {
1075
+ result = fieldName;
1076
+ }
1077
+ }
1078
+ return result;
1079
+ }
1035
1080
  /**
1036
1081
  * Returns details for a redundant cleanup statement, or null when the
1037
1082
  * statement is anything other than a redundant `this.<field>.<destroy|complete>()` call.
@@ -1042,25 +1087,16 @@
1042
1087
  */ function matchRedundantCleanupStatement(statement, wrappedFields) {
1043
1088
  var result = null;
1044
1089
  if (statement.type === 'ExpressionStatement') {
1045
- var _call_arguments, _call_callee;
1046
- var call = statement.expression;
1047
- var isZeroArgMemberCall = (call === null || call === void 0 ? void 0 : call.type) === 'CallExpression' && ((_call_arguments = call.arguments) === null || _call_arguments === void 0 ? void 0 : _call_arguments.length) === 0 && ((_call_callee = call.callee) === null || _call_callee === void 0 ? void 0 : _call_callee.type) === 'MemberExpression';
1048
- if (isZeroArgMemberCall) {
1049
- var _callee_property, _callee_object, _callee_object_property;
1050
- var callee = call.callee;
1051
- var methodName = !callee.computed && ((_callee_property = callee.property) === null || _callee_property === void 0 ? void 0 : _callee_property.type) === 'Identifier' ? callee.property.name : null;
1052
- if (methodName && REDUNDANT_METHODS.has(methodName) && ((_callee_object = callee.object) === null || _callee_object === void 0 ? void 0 : _callee_object.type) === 'MemberExpression' && ((_callee_object_property = callee.object.property) === null || _callee_object_property === void 0 ? void 0 : _callee_object_property.type) === 'Identifier' && !callee.object.computed) {
1053
- var fieldName = callee.object.property.name;
1054
- var wrapper = isThisMemberAccess(callee.object, fieldName) ? wrappedFields.get(fieldName) : undefined;
1055
- if (wrapper) {
1056
- result = {
1057
- statement: statement,
1058
- fieldName: fieldName,
1059
- method: methodName,
1060
- wrapper: wrapper
1061
- };
1062
- }
1063
- }
1090
+ var methodCall = getRedundantMethodCall(statement.expression);
1091
+ var fieldName = methodCall ? getThisFieldName(methodCall.callee.object) : null;
1092
+ var wrapper = fieldName ? wrappedFields.get(fieldName) : undefined;
1093
+ if (methodCall && fieldName && wrapper) {
1094
+ result = {
1095
+ statement: statement,
1096
+ fieldName: fieldName,
1097
+ method: methodCall.methodName,
1098
+ wrapper: wrapper
1099
+ };
1064
1100
  }
1065
1101
  }
1066
1102
  return result;
@@ -1084,19 +1120,1438 @@
1084
1120
  }
1085
1121
 
1086
1122
  /**
1087
- * ESLint plugin for dbx-web rules.
1123
+ * Initializer call names that produce a computed Signal whose property must end with `Signal`.
1124
+ */ var COMPUTED_INITIALIZERS = new Set([
1125
+ 'computed'
1126
+ ]);
1127
+ /**
1128
+ * Initializer call names that produce a raw signal-input whose property must NOT end with `Signal`.
1088
1129
  *
1089
- * Register as a plugin in your flat ESLint config, then enable individual rules
1090
- * under the chosen plugin prefix (e.g. 'dereekb-dbx-web/require-clean-subscription').
1091
- */ var dbxWebEslintPlugin = {
1092
- rules: {
1093
- 'require-clean-subscription': dbxWebRequireCleanSubscriptionRule,
1094
- 'require-complete-on-destroy': dbxWebRequireCompleteOnDestroyRule,
1095
- 'no-redundant-on-destroy': dbxWebNoRedundantOnDestroyRule
1130
+ * Includes `input.required(...)` (handled below via CallExpression callee inspection) and
1131
+ * `model.required(...)` those are recognized when the callee's MemberExpression `object` name
1132
+ * is in this set.
1133
+ */ var INPUT_INITIALIZERS$1 = new Set([
1134
+ 'input',
1135
+ 'model'
1136
+ ]);
1137
+ /**
1138
+ * Suffix that distinguishes computed signals from raw input signals.
1139
+ */ var SIGNAL_SUFFIX = 'Signal';
1140
+ /**
1141
+ * ESLint rule that enforces the dbx-components Angular signal naming convention:
1142
+ *
1143
+ * - Class properties initialized with `computed(...)` must end with `Signal`.
1144
+ * - Class properties initialized with `input(...)`, `input.required(...)`, `model(...)`,
1145
+ * or `model.required(...)` must NOT end with `Signal`.
1146
+ *
1147
+ * Fires only on classes decorated with `@Component`, `@Directive`, or `@Pipe` from
1148
+ * `@angular/core`, and only when the relevant initializer identifier is imported from
1149
+ * `@angular/core`.
1150
+ *
1151
+ * Not auto-fixable: renaming a class field also requires updating every reference in the
1152
+ * class body and any associated templates, which is outside the safe scope of an ESLint
1153
+ * autofix.
1154
+ *
1155
+ * @see `dbx__note__angular-conventions` → ANG-C2 Computed Signal Naming.
1156
+ */ var DBX_WEB_REQUIRE_COMPUTED_SIGNAL_SUFFIX_RULE = {
1157
+ meta: {
1158
+ type: 'suggestion',
1159
+ fixable: undefined,
1160
+ docs: {
1161
+ description: 'Require the `Signal` suffix on computed() class properties and disallow it on input()/model() class properties in Angular component classes.',
1162
+ recommended: true
1163
+ },
1164
+ messages: {
1165
+ missingSignalSuffix: "Computed signal '{{property}}' must end with the 'Signal' suffix (e.g. '{{suggested}}') to distinguish it from raw input signals.",
1166
+ signalSuffixOnInput: "Raw input signal '{{property}}' must NOT end with the 'Signal' suffix — reserve that for computed signals."
1167
+ },
1168
+ schema: []
1169
+ },
1170
+ create: function create(context) {
1171
+ var registry = createImportRegistry();
1172
+ var visitClass = function visitClass(classNode) {
1173
+ var _ref;
1174
+ var _classNode_body;
1175
+ var matched = findAngularComponentDecorator(classNode, registry);
1176
+ if (!matched) {
1177
+ return;
1178
+ }
1179
+ var members = (_ref = (_classNode_body = classNode.body) === null || _classNode_body === void 0 ? void 0 : _classNode_body.body) !== null && _ref !== void 0 ? _ref : [];
1180
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
1181
+ try {
1182
+ for(var _iterator = members[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
1183
+ var member = _step.value;
1184
+ if (member.type !== 'PropertyDefinition' || isStaticProperty(member) || isDeclareProperty(member)) {
1185
+ continue;
1186
+ }
1187
+ var propName = getClassMemberName(member);
1188
+ var initializer = member.value;
1189
+ if (!propName || (initializer === null || initializer === void 0 ? void 0 : initializer.type) !== 'CallExpression') {
1190
+ continue;
1191
+ }
1192
+ var initializerKind = classifyInitializer(initializer, registry);
1193
+ if (initializerKind === 'computed') {
1194
+ if (!propName.endsWith(SIGNAL_SUFFIX)) {
1195
+ var _member_key;
1196
+ context.report({
1197
+ node: (_member_key = member.key) !== null && _member_key !== void 0 ? _member_key : member,
1198
+ messageId: 'missingSignalSuffix',
1199
+ data: {
1200
+ property: propName,
1201
+ suggested: "".concat(propName).concat(SIGNAL_SUFFIX)
1202
+ }
1203
+ });
1204
+ }
1205
+ } else if (initializerKind === 'input' && propName.endsWith(SIGNAL_SUFFIX) && propName !== SIGNAL_SUFFIX) {
1206
+ var _member_key1;
1207
+ context.report({
1208
+ node: (_member_key1 = member.key) !== null && _member_key1 !== void 0 ? _member_key1 : member,
1209
+ messageId: 'signalSuffixOnInput',
1210
+ data: {
1211
+ property: propName
1212
+ }
1213
+ });
1214
+ }
1215
+ }
1216
+ } catch (err) {
1217
+ _didIteratorError = true;
1218
+ _iteratorError = err;
1219
+ } finally{
1220
+ try {
1221
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
1222
+ _iterator.return();
1223
+ }
1224
+ } finally{
1225
+ if (_didIteratorError) {
1226
+ throw _iteratorError;
1227
+ }
1228
+ }
1229
+ }
1230
+ };
1231
+ return {
1232
+ ImportDeclaration: function ImportDeclaration(node) {
1233
+ trackImportDeclaration(registry, node);
1234
+ },
1235
+ ClassDeclaration: function ClassDeclaration(classNode) {
1236
+ visitClass(classNode);
1237
+ },
1238
+ ClassExpression: function ClassExpression(classNode) {
1239
+ visitClass(classNode);
1240
+ }
1241
+ };
1096
1242
  }
1097
1243
  };
1244
+ /**
1245
+ * Classifies a CallExpression initializer as a computed signal, raw input signal, or neither.
1246
+ *
1247
+ * Recognizes:
1248
+ * - `computed(...)` → `'computed'`
1249
+ * - `input(...)`, `input.required(...)` → `'input'`
1250
+ * - `model(...)`, `model.required(...)` → `'input'`
1251
+ *
1252
+ * Each form requires the root identifier to be imported from `@angular/core`.
1253
+ *
1254
+ * @param callExpression - The CallExpression AST node serving as the property initializer.
1255
+ * @param registry - The file's import registry.
1256
+ * @returns The initializer kind, or `null` when the call is unrelated.
1257
+ */ function classifyInitializer(callExpression, registry) {
1258
+ var _callee_object, _callee_property;
1259
+ var callee = callExpression.callee;
1260
+ var result = null;
1261
+ if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'Identifier') {
1262
+ var name = callee.name;
1263
+ if (COMPUTED_INITIALIZERS.has(name) && isImportedFrom(registry, name, ANGULAR_CORE_MODULE)) {
1264
+ result = 'computed';
1265
+ } else if (INPUT_INITIALIZERS$1.has(name) && isImportedFrom(registry, name, ANGULAR_CORE_MODULE)) {
1266
+ result = 'input';
1267
+ }
1268
+ } else if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'MemberExpression' && callee.computed === false && ((_callee_object = callee.object) === null || _callee_object === void 0 ? void 0 : _callee_object.type) === 'Identifier' && ((_callee_property = callee.property) === null || _callee_property === void 0 ? void 0 : _callee_property.type) === 'Identifier' && callee.property.name === 'required') {
1269
+ var rootName = callee.object.name;
1270
+ if (INPUT_INITIALIZERS$1.has(rootName) && isImportedFrom(registry, rootName, ANGULAR_CORE_MODULE)) {
1271
+ result = 'input';
1272
+ }
1273
+ }
1274
+ return result;
1275
+ }
1276
+
1277
+ /**
1278
+ * Initializer call identifiers that produce an Angular signal input.
1279
+ *
1280
+ * Includes the bare `input(...)` form and the `input.required(...)` member form.
1281
+ * `model()` / `model.required()` are intentionally excluded — two-way bindings are
1282
+ * rare and counting them would inflate the threshold for components that mix them
1283
+ * sparingly. Extending the set is a one-line change.
1284
+ */ var INPUT_INITIALIZERS = new Set([
1285
+ 'input'
1286
+ ]);
1287
+ /**
1288
+ * Default cap on the number of `input(...)` / `input.required(...)` properties a
1289
+ * single Angular component-tier class may declare before the rule fires.
1290
+ */ var DEFAULT_INPUT_THRESHOLD = 3;
1291
+ /**
1292
+ * ESLint rule that flags `@Component` / `@Directive` / `@Pipe` classes which
1293
+ * declare more than `threshold` (default 3) signal-input properties.
1294
+ *
1295
+ * When a component-tier class drifts past the threshold, the convention is to
1296
+ * consolidate the loose inputs into a single config-typed input, e.g.
1297
+ * `config = input<Maybe<DbxFooConfig>>()`. This rule does not enforce a specific
1298
+ * property name or shape — it is purely a count, analogous to the workspace's
1299
+ * `dereekb-util/prefer-config-object` rule for function parameters.
1300
+ *
1301
+ * Only `input(...)` and `input.required(...)` calls whose root identifier is
1302
+ * imported from `@angular/core` are counted. Static and `declare` members are
1303
+ * ignored, as are non-decorated classes and imports from other modules.
1304
+ *
1305
+ * Not auto-fixable: consolidating loose inputs into a config interface is a
1306
+ * design-level refactor that updates the template, the consuming sites, and the
1307
+ * type surface — outside the safe scope of an ESLint autofix.
1308
+ *
1309
+ * @see `dbx__note__angular-conventions` → ANG-C1 Component Config Input.
1310
+ */ var DBX_WEB_REQUIRE_COMPONENT_CONFIG_INPUT_RULE = {
1311
+ meta: {
1312
+ type: 'suggestion',
1313
+ fixable: undefined,
1314
+ docs: {
1315
+ description: 'Disallow more than `threshold` (default 3) signal-input properties on a single @Component/@Directive/@Pipe class; consolidate them into a single config-typed input.',
1316
+ recommended: true
1317
+ },
1318
+ messages: {
1319
+ tooManySignalInputs: "Class '{{className}}' declares {{count}} signal inputs (more than {{threshold}}). Consolidate them into a single config-typed input (e.g. `config = input<Maybe<...Config>>()`). See dbx__note__angular-conventions → ANG-C1."
1320
+ },
1321
+ schema: [
1322
+ {
1323
+ type: 'object',
1324
+ properties: {
1325
+ threshold: {
1326
+ type: 'number',
1327
+ minimum: 0,
1328
+ description: 'Maximum number of signal-input properties allowed before the rule reports. Defaults to 3.'
1329
+ }
1330
+ },
1331
+ additionalProperties: false
1332
+ }
1333
+ ]
1334
+ },
1335
+ create: function create(context) {
1336
+ var _ref;
1337
+ var _context_options;
1338
+ var registry = createImportRegistry();
1339
+ var options = (_ref = (_context_options = context.options) === null || _context_options === void 0 ? void 0 : _context_options[0]) !== null && _ref !== void 0 ? _ref : {};
1340
+ var threshold = typeof options.threshold === 'number' ? options.threshold : DEFAULT_INPUT_THRESHOLD;
1341
+ var visitClass = function visitClass(classNode) {
1342
+ var _ref;
1343
+ var _classNode_body;
1344
+ var matched = findAngularComponentDecorator(classNode, registry);
1345
+ if (!matched) {
1346
+ return;
1347
+ }
1348
+ var members = (_ref = (_classNode_body = classNode.body) === null || _classNode_body === void 0 ? void 0 : _classNode_body.body) !== null && _ref !== void 0 ? _ref : [];
1349
+ var inputCount = 0;
1350
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
1351
+ try {
1352
+ for(var _iterator = members[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
1353
+ var member = _step.value;
1354
+ if (member.type !== 'PropertyDefinition' || isStaticProperty(member) || isDeclareProperty(member)) {
1355
+ continue;
1356
+ }
1357
+ var initializer = member.value;
1358
+ if ((initializer === null || initializer === void 0 ? void 0 : initializer.type) === 'CallExpression' && isAngularInputCall(initializer, registry)) {
1359
+ inputCount += 1;
1360
+ }
1361
+ }
1362
+ } catch (err) {
1363
+ _didIteratorError = true;
1364
+ _iteratorError = err;
1365
+ } finally{
1366
+ try {
1367
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
1368
+ _iterator.return();
1369
+ }
1370
+ } finally{
1371
+ if (_didIteratorError) {
1372
+ throw _iteratorError;
1373
+ }
1374
+ }
1375
+ }
1376
+ if (inputCount > threshold) {
1377
+ var _ref1, _classNode_id;
1378
+ var _classNode_id1;
1379
+ var className = (_ref1 = (_classNode_id1 = classNode.id) === null || _classNode_id1 === void 0 ? void 0 : _classNode_id1.name) !== null && _ref1 !== void 0 ? _ref1 : '<anonymous>';
1380
+ context.report({
1381
+ node: (_classNode_id = classNode.id) !== null && _classNode_id !== void 0 ? _classNode_id : classNode,
1382
+ messageId: 'tooManySignalInputs',
1383
+ data: {
1384
+ className: className,
1385
+ count: String(inputCount),
1386
+ threshold: String(threshold)
1387
+ }
1388
+ });
1389
+ }
1390
+ };
1391
+ return {
1392
+ ImportDeclaration: function ImportDeclaration(node) {
1393
+ trackImportDeclaration(registry, node);
1394
+ },
1395
+ ClassDeclaration: function ClassDeclaration(classNode) {
1396
+ visitClass(classNode);
1397
+ },
1398
+ ClassExpression: function ClassExpression(classNode) {
1399
+ visitClass(classNode);
1400
+ }
1401
+ };
1402
+ }
1403
+ };
1404
+ /**
1405
+ * Returns true when `callExpression` is a call to an Angular signal-input
1406
+ * factory — either `input(...)` (Identifier callee) or `input.required(...)`
1407
+ * (MemberExpression callee whose root identifier is `input`) — and the root
1408
+ * identifier was imported from `@angular/core`.
1409
+ *
1410
+ * @param callExpression - The CallExpression AST node serving as a property initializer.
1411
+ * @param registry - The file's import registry.
1412
+ * @returns True when the call should be counted as a signal input.
1413
+ */ function isAngularInputCall(callExpression, registry) {
1414
+ var _callee_object, _callee_property;
1415
+ var callee = callExpression.callee;
1416
+ var result = false;
1417
+ if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'Identifier') {
1418
+ var name = callee.name;
1419
+ if (INPUT_INITIALIZERS.has(name) && isImportedFrom(registry, name, ANGULAR_CORE_MODULE)) {
1420
+ result = true;
1421
+ }
1422
+ } else if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'MemberExpression' && callee.computed === false && ((_callee_object = callee.object) === null || _callee_object === void 0 ? void 0 : _callee_object.type) === 'Identifier' && ((_callee_property = callee.property) === null || _callee_property === void 0 ? void 0 : _callee_property.type) === 'Identifier' && callee.property.name === 'required') {
1423
+ var rootName = callee.object.name;
1424
+ if (INPUT_INITIALIZERS.has(rootName) && isImportedFrom(registry, rootName, ANGULAR_CORE_MODULE)) {
1425
+ result = true;
1426
+ }
1427
+ }
1428
+ return result;
1429
+ }
1430
+
1431
+ function _array_like_to_array(arr, len) {
1432
+ if (len == null || len > arr.length) len = arr.length;
1433
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
1434
+ return arr2;
1435
+ }
1436
+ function _array_with_holes(arr) {
1437
+ if (Array.isArray(arr)) return arr;
1438
+ }
1439
+ function _array_without_holes(arr) {
1440
+ if (Array.isArray(arr)) return _array_like_to_array(arr);
1441
+ }
1442
+ function _iterable_to_array(iter) {
1443
+ if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
1444
+ }
1445
+ function _iterable_to_array_limit(arr, i) {
1446
+ var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
1447
+ if (_i == null) return;
1448
+ var _arr = [];
1449
+ var _n = true;
1450
+ var _d = false;
1451
+ var _s, _e;
1452
+ try {
1453
+ for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
1454
+ _arr.push(_s.value);
1455
+ if (i && _arr.length === i) break;
1456
+ }
1457
+ } catch (err) {
1458
+ _d = true;
1459
+ _e = err;
1460
+ } finally{
1461
+ try {
1462
+ if (!_n && _i["return"] != null) _i["return"]();
1463
+ } finally{
1464
+ if (_d) throw _e;
1465
+ }
1466
+ }
1467
+ return _arr;
1468
+ }
1469
+ function _non_iterable_rest() {
1470
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
1471
+ }
1472
+ function _non_iterable_spread() {
1473
+ throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
1474
+ }
1475
+ function _sliced_to_array(arr, i) {
1476
+ return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest();
1477
+ }
1478
+ function _to_consumable_array(arr) {
1479
+ return _array_without_holes(arr) || _iterable_to_array(arr) || _unsupported_iterable_to_array(arr) || _non_iterable_spread();
1480
+ }
1481
+ function _type_of(obj) {
1482
+ "@swc/helpers - typeof";
1483
+ return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
1484
+ }
1485
+ function _unsupported_iterable_to_array(o, minLen) {
1486
+ if (!o) return;
1487
+ if (typeof o === "string") return _array_like_to_array(o, minLen);
1488
+ var n = Object.prototype.toString.call(o).slice(8, -1);
1489
+ if (n === "Object" && o.constructor) n = o.constructor.name;
1490
+ if (n === "Map" || n === "Set") return Array.from(n);
1491
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
1492
+ }
1493
+ /**
1494
+ * Module that exposes the `toSignal` factory.
1495
+ */ var ANGULAR_CORE_RXJS_INTEROP_MODULE = '@angular/core/rxjs-interop';
1496
+ /**
1497
+ * Name of the Angular `computed` factory whose callbacks this rule inspects.
1498
+ */ var COMPUTED_INITIALIZER_NAME = 'computed';
1499
+ /**
1500
+ * Bare-identifier signal factories exported from `@angular/core`.
1501
+ *
1502
+ * Each name corresponds to a function that returns a Signal/InputSignal/
1503
+ * WritableSignal/ModelSignal — the kind of getter the rule needs to track
1504
+ * across class properties and module-level `const`s.
1505
+ */ var ANGULAR_CORE_SIGNAL_FACTORIES = new Set([
1506
+ 'signal',
1507
+ 'computed',
1508
+ 'input',
1509
+ 'model',
1510
+ 'linkedSignal'
1511
+ ]);
1512
+ /**
1513
+ * `Identifier.required(...)` member-form factories. Each entry's value is the
1514
+ * root identifier whose `required` property returns another signal factory.
1515
+ *
1516
+ * Example: `input.required<string>()` — `input` is the root identifier and
1517
+ * the result is still an InputSignal.
1518
+ */ var ANGULAR_CORE_REQUIRED_FACTORIES = new Set([
1519
+ 'input',
1520
+ 'model'
1521
+ ]);
1522
+ /**
1523
+ * Bare-identifier signal factories exported from `@angular/core/rxjs-interop`.
1524
+ */ var ANGULAR_CORE_RXJS_INTEROP_SIGNAL_FACTORIES = new Set([
1525
+ 'toSignal'
1526
+ ]);
1527
+ /**
1528
+ * Signal type names exported from `@angular/core`. A property or variable
1529
+ * whose declared type is one of these (and whose root identifier was imported
1530
+ * from `@angular/core`) is treated as a signal getter, even when its
1531
+ * initializer is not a recognized signal factory call (e.g. produced by a
1532
+ * helper or returned from a base class).
1533
+ */ var ANGULAR_CORE_SIGNAL_TYPE_NAMES = new Set([
1534
+ 'Signal',
1535
+ 'WritableSignal',
1536
+ 'InputSignal',
1537
+ 'InputSignalWithTransform',
1538
+ 'ModelSignal'
1539
+ ]);
1540
+ /**
1541
+ * Maximum number of characters from the offending call expression that should
1542
+ * appear in the report message. Long expressions are truncated so the message
1543
+ * stays readable in editor tooltips.
1544
+ */ var CALL_PREVIEW_MAX_LENGTH = 40;
1545
+ /**
1546
+ * ESLint rule that requires every signal read inside a `computed(() => { ... })`
1547
+ * callback to appear in an unconditional, top-level position rather than
1548
+ * inside a branching path (if/else, ternary, short-circuit, switch case,
1549
+ * loop body, catch handler).
1550
+ *
1551
+ * Angular `computed` re-tracks its dependencies on every run, so a signal
1552
+ * read that only happens inside one branch is not registered as a dependency
1553
+ * when the other branch executes. When that signal subsequently changes the
1554
+ * computed does not recompute and the value goes stale. Reading every signal
1555
+ * up front — before any branching — keeps the dependency set stable.
1556
+ *
1557
+ * To avoid false positives on plain methods and utility functions, the rule
1558
+ * only flags calls whose name can be statically traced back to a signal
1559
+ * factory:
1560
+ *
1561
+ * - `this.<name>()` is flagged when `<name>` is a class property initialized
1562
+ * with one of `signal`, `computed`, `input`, `input.required`, `model`,
1563
+ * `model.required`, `linkedSignal` (from `@angular/core`), or `toSignal`
1564
+ * (from `@angular/core/rxjs-interop`).
1565
+ * - `<name>()` (bare identifier) is flagged when `<name>` is a module-level
1566
+ * `const` initialized with one of those factories.
1567
+ *
1568
+ * Everything else — calls with arguments, chained property accesses on
1569
+ * services, calls on local loop variables, calls on globals — is left
1570
+ * alone. Cross-class signal tracking (e.g. `this.someService.someSignal()`)
1571
+ * is intentionally out of scope: it would require type analysis to
1572
+ * distinguish signal getters from plain getter methods, and the rule
1573
+ * prefers a clean report set over partial coverage.
1574
+ *
1575
+ * Only `computed` identifiers imported from `@angular/core` are considered.
1576
+ * Nested function expressions (callbacks passed to `.map` / `.filter` etc.)
1577
+ * are not inspected because Angular does not synchronously track signals
1578
+ * read inside them.
1579
+ *
1580
+ * Auto-fix: for each flagged callback, the fix inserts one
1581
+ * `const <localName> = this.<signalName>();` (or `const <localName> = <signalName>();`
1582
+ * for module-scope captures) at the top of the callback body and replaces
1583
+ * every flagged call with `<localName>`. The local name is the signal name
1584
+ * with the trailing `Signal` suffix removed when present (`xSignal` → `x`).
1585
+ * If that name would shadow an existing local binding in the callback, the
1586
+ * fix is skipped for that signal to avoid generating a syntax error.
1587
+ * Expression-body callbacks (`computed(() => …)`) are converted to block
1588
+ * bodies as part of the fix; the previous expression becomes the `return`
1589
+ * value.
1590
+ */ var DBX_WEB_REQUIRE_TOP_LEVEL_COMPUTED_SIGNALS_RULE = {
1591
+ meta: {
1592
+ type: 'problem',
1593
+ fixable: 'code',
1594
+ docs: {
1595
+ description: 'Require signal reads inside a computed() callback to occur in unconditional top-level statements, not inside if/else, ternary, short-circuit, switch, loop, or catch branches.',
1596
+ recommended: true
1597
+ },
1598
+ messages: {
1599
+ conditionalSignalRead: "Signal read '{{call}}' is inside a conditional execution path of computed(); hoist it to an unconditional top-level read before the branch so the computed tracks it on every run."
1600
+ },
1601
+ schema: []
1602
+ },
1603
+ create: function create(context) {
1604
+ var imports = createImportRegistry();
1605
+ var signals = {
1606
+ classSignalProps: new WeakMap(),
1607
+ moduleSignalNames: new Set()
1608
+ };
1609
+ var visitClass = function visitClass(classNode) {
1610
+ collectClassSignalProperties(classNode, imports, signals);
1611
+ };
1612
+ var inspectComputedCall = function inspectComputedCall(callNode) {
1613
+ var _callNode_arguments;
1614
+ if (!isComputedCall(callNode, imports)) {
1615
+ return;
1616
+ }
1617
+ var callback = (_callNode_arguments = callNode.arguments) === null || _callNode_arguments === void 0 ? void 0 : _callNode_arguments[0];
1618
+ if (!callback || !isFunctionNode(callback) || !callback.body) {
1619
+ return;
1620
+ }
1621
+ var enclosingClass = findEnclosingClass(callNode);
1622
+ var classSignalNames = enclosingClass ? signals.classSignalProps.get(enclosingClass) : null;
1623
+ var violations = [];
1624
+ // The function body itself is the entry point. Conditional state starts
1625
+ // as `false` — top-level statements in the body run unconditionally.
1626
+ walk(callback.body, false, {
1627
+ classSignalNames: classSignalNames,
1628
+ moduleSignalNames: signals.moduleSignalNames,
1629
+ violations: violations
1630
+ });
1631
+ if (violations.length === 0) {
1632
+ return;
1633
+ }
1634
+ reportViolations(callback, violations, context);
1635
+ };
1636
+ return {
1637
+ ImportDeclaration: function ImportDeclaration(node) {
1638
+ trackImportDeclaration(imports, node);
1639
+ },
1640
+ VariableDeclaration: function VariableDeclaration(node) {
1641
+ collectModuleSignalConsts(node, imports, signals);
1642
+ },
1643
+ ClassDeclaration: function ClassDeclaration(classNode) {
1644
+ visitClass(classNode);
1645
+ },
1646
+ ClassExpression: function ClassExpression(classNode) {
1647
+ visitClass(classNode);
1648
+ },
1649
+ CallExpression: function CallExpression(node) {
1650
+ inspectComputedCall(node);
1651
+ }
1652
+ };
1653
+ }
1654
+ };
1655
+ /**
1656
+ * Returns true when `node` is a `computed(...)` call whose `computed`
1657
+ * identifier was imported from `@angular/core`.
1658
+ *
1659
+ * @param node - The CallExpression AST node to test.
1660
+ * @param imports - The file's import registry.
1661
+ * @returns True when the call refers to Angular's `computed`.
1662
+ */ function isComputedCall(node, imports) {
1663
+ var _node_callee;
1664
+ return (node === null || node === void 0 ? void 0 : node.type) === 'CallExpression' && ((_node_callee = node.callee) === null || _node_callee === void 0 ? void 0 : _node_callee.type) === 'Identifier' && node.callee.name === COMPUTED_INITIALIZER_NAME && isImportedFrom(imports, COMPUTED_INITIALIZER_NAME, ANGULAR_CORE_MODULE);
1665
+ }
1666
+ /**
1667
+ * Returns true when `callExpression` is a call to one of the Angular signal
1668
+ * factories whose return value is a Signal/InputSignal/WritableSignal/
1669
+ * ModelSignal. Handles bare-identifier calls (`signal(...)`), required
1670
+ * member-form calls (`input.required(...)`, `model.required(...)`), and
1671
+ * `toSignal(...)` from `@angular/core/rxjs-interop`.
1672
+ *
1673
+ * @param callExpression - The CallExpression AST node serving as an initializer.
1674
+ * @param imports - The file's import registry.
1675
+ * @returns True when the call returns a signal.
1676
+ */ function isSignalFactoryCall(callExpression, imports) {
1677
+ var _callee_object, _callee_property;
1678
+ var callee = callExpression === null || callExpression === void 0 ? void 0 : callExpression.callee;
1679
+ var result = false;
1680
+ if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'Identifier') {
1681
+ var name = callee.name;
1682
+ if (ANGULAR_CORE_SIGNAL_FACTORIES.has(name) && isImportedFrom(imports, name, ANGULAR_CORE_MODULE)) {
1683
+ result = true;
1684
+ } else if (ANGULAR_CORE_RXJS_INTEROP_SIGNAL_FACTORIES.has(name) && isImportedFrom(imports, name, ANGULAR_CORE_RXJS_INTEROP_MODULE)) {
1685
+ result = true;
1686
+ }
1687
+ } else if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'MemberExpression' && callee.computed === false && ((_callee_object = callee.object) === null || _callee_object === void 0 ? void 0 : _callee_object.type) === 'Identifier' && ((_callee_property = callee.property) === null || _callee_property === void 0 ? void 0 : _callee_property.type) === 'Identifier' && callee.property.name === 'required') {
1688
+ var rootName = callee.object.name;
1689
+ if (ANGULAR_CORE_REQUIRED_FACTORIES.has(rootName) && isImportedFrom(imports, rootName, ANGULAR_CORE_MODULE)) {
1690
+ result = true;
1691
+ }
1692
+ }
1693
+ return result;
1694
+ }
1695
+ /**
1696
+ * Returns true when `typeAnnotation` is a TypeScript type annotation node whose
1697
+ * root type name is one of the Angular signal types and resolves to an
1698
+ * `@angular/core` import (e.g. `Signal<number>`, `InputSignal<string>`).
1699
+ *
1700
+ * Accepts both the `TSTypeAnnotation` wrapper and the inner `TSTypeReference`
1701
+ * form. Returns false for any other shape (unions, intersections, generic
1702
+ * wrappers, etc.) — the rule prefers under-coverage to false positives.
1703
+ *
1704
+ * @param typeAnnotation - The TSTypeAnnotation / TSTypeReference AST node.
1705
+ * @param imports - The file's import registry.
1706
+ * @returns True when the type annotation names an Angular signal type.
1707
+ */ function isSignalTypeAnnotation(typeAnnotation, imports) {
1708
+ var _typeNode_typeName;
1709
+ var typeNode = typeAnnotation;
1710
+ if ((typeNode === null || typeNode === void 0 ? void 0 : typeNode.type) === 'TSTypeAnnotation') {
1711
+ typeNode = typeNode.typeAnnotation;
1712
+ }
1713
+ var result = false;
1714
+ if ((typeNode === null || typeNode === void 0 ? void 0 : typeNode.type) === 'TSTypeReference' && ((_typeNode_typeName = typeNode.typeName) === null || _typeNode_typeName === void 0 ? void 0 : _typeNode_typeName.type) === 'Identifier') {
1715
+ var name = typeNode.typeName.name;
1716
+ if (ANGULAR_CORE_SIGNAL_TYPE_NAMES.has(name) && isImportedFrom(imports, name, ANGULAR_CORE_MODULE)) {
1717
+ result = true;
1718
+ }
1719
+ }
1720
+ return result;
1721
+ }
1722
+ /**
1723
+ * Scans the class body for `PropertyDefinition` members whose initializer is
1724
+ * a signal factory call, and stores their names in `signals.classSignalProps`.
1725
+ *
1726
+ * Static and `declare` properties are ignored. Property names that are not
1727
+ * simple identifiers (e.g. computed keys, symbols) are also ignored.
1728
+ *
1729
+ * @param classNode - The ClassDeclaration / ClassExpression AST node.
1730
+ * @param imports - The file's import registry.
1731
+ * @param signals - The signal registry that is mutated with the results.
1732
+ */ function collectClassSignalProperties(classNode, imports, signals) {
1733
+ var _ref;
1734
+ var _classNode_body;
1735
+ var members = (_ref = (_classNode_body = classNode.body) === null || _classNode_body === void 0 ? void 0 : _classNode_body.body) !== null && _ref !== void 0 ? _ref : [];
1736
+ var names = new Set();
1737
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
1738
+ try {
1739
+ for(var _iterator = members[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
1740
+ var member = _step.value;
1741
+ if ((member === null || member === void 0 ? void 0 : member.type) !== 'PropertyDefinition' || member.static === true || member.declare === true || member.computed === true) {
1742
+ continue;
1743
+ }
1744
+ var key = member.key;
1745
+ var initializer = member.value;
1746
+ var propName = null;
1747
+ if ((key === null || key === void 0 ? void 0 : key.type) === 'Identifier') {
1748
+ propName = key.name;
1749
+ } else if ((key === null || key === void 0 ? void 0 : key.type) === 'Literal' && typeof key.value === 'string') {
1750
+ propName = key.value;
1751
+ }
1752
+ if (propName) {
1753
+ var initializerIsSignal = (initializer === null || initializer === void 0 ? void 0 : initializer.type) === 'CallExpression' && isSignalFactoryCall(initializer, imports);
1754
+ var typeIsSignal = member.typeAnnotation && isSignalTypeAnnotation(member.typeAnnotation, imports);
1755
+ if (initializerIsSignal || typeIsSignal) {
1756
+ names.add(propName);
1757
+ }
1758
+ }
1759
+ }
1760
+ } catch (err) {
1761
+ _didIteratorError = true;
1762
+ _iteratorError = err;
1763
+ } finally{
1764
+ try {
1765
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
1766
+ _iterator.return();
1767
+ }
1768
+ } finally{
1769
+ if (_didIteratorError) {
1770
+ throw _iteratorError;
1771
+ }
1772
+ }
1773
+ }
1774
+ signals.classSignalProps.set(classNode, names);
1775
+ }
1776
+ /**
1777
+ * Scans a `VariableDeclaration` node that lives directly inside the program
1778
+ * body for `const` declarators whose initializer is a signal factory call,
1779
+ * adding their names to `signals.moduleSignalNames`.
1780
+ *
1781
+ * Non-`const` declarations, declarations nested inside functions or blocks,
1782
+ * and declarations whose initializer is not a signal factory are skipped.
1783
+ *
1784
+ * @param node - The VariableDeclaration AST node.
1785
+ * @param imports - The file's import registry.
1786
+ * @param signals - The signal registry that is mutated with the results.
1787
+ */ function collectModuleSignalConsts(node, imports, signals) {
1788
+ var _node_declarations;
1789
+ var _node_parent;
1790
+ if ((node === null || node === void 0 ? void 0 : node.kind) !== 'const' || ((_node_parent = node.parent) === null || _node_parent === void 0 ? void 0 : _node_parent.type) !== 'Program') {
1791
+ return;
1792
+ }
1793
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
1794
+ try {
1795
+ for(var _iterator = ((_node_declarations = node.declarations) !== null && _node_declarations !== void 0 ? _node_declarations : [])[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
1796
+ var declarator = _step.value;
1797
+ var id = declarator === null || declarator === void 0 ? void 0 : declarator.id;
1798
+ var init = declarator === null || declarator === void 0 ? void 0 : declarator.init;
1799
+ if ((id === null || id === void 0 ? void 0 : id.type) === 'Identifier') {
1800
+ var initIsSignal = (init === null || init === void 0 ? void 0 : init.type) === 'CallExpression' && isSignalFactoryCall(init, imports);
1801
+ var typeIsSignal = id.typeAnnotation && isSignalTypeAnnotation(id.typeAnnotation, imports);
1802
+ if (initIsSignal || typeIsSignal) {
1803
+ signals.moduleSignalNames.add(id.name);
1804
+ }
1805
+ }
1806
+ }
1807
+ } catch (err) {
1808
+ _didIteratorError = true;
1809
+ _iteratorError = err;
1810
+ } finally{
1811
+ try {
1812
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
1813
+ _iterator.return();
1814
+ }
1815
+ } finally{
1816
+ if (_didIteratorError) {
1817
+ throw _iteratorError;
1818
+ }
1819
+ }
1820
+ }
1821
+ }
1822
+ /**
1823
+ * Walks up from `node` via `parent` references to find the nearest enclosing
1824
+ * `ClassDeclaration` or `ClassExpression`, or null if there isn't one.
1825
+ *
1826
+ * @param node - The starting AST node.
1827
+ * @returns The enclosing class node, or null.
1828
+ */ function findEnclosingClass(node) {
1829
+ var current = node.parent;
1830
+ var result = null;
1831
+ while(current && !result){
1832
+ if (current.type === 'ClassDeclaration' || current.type === 'ClassExpression') {
1833
+ result = current;
1834
+ } else {
1835
+ current = current.parent;
1836
+ }
1837
+ }
1838
+ return result;
1839
+ }
1840
+ /**
1841
+ * Returns true when `node` is any function-like AST node (arrow, expression,
1842
+ * declaration). Used both to find the computed callback and to stop the walk
1843
+ * when it would otherwise descend into a nested function body.
1844
+ *
1845
+ * @param node - The AST node to test.
1846
+ * @returns True when `node` is a function-like node.
1847
+ */ function isFunctionNode(node) {
1848
+ return (node === null || node === void 0 ? void 0 : node.type) === 'ArrowFunctionExpression' || (node === null || node === void 0 ? void 0 : node.type) === 'FunctionExpression' || (node === null || node === void 0 ? void 0 : node.type) === 'FunctionDeclaration';
1849
+ }
1850
+ /**
1851
+ * Recursively walks the body of a `computed(...)` callback. For each
1852
+ * zero-argument `CallExpression` that can be statically traced to a known
1853
+ * signal getter, records a violation when `conditional` is true. Stops at
1854
+ * any nested function boundary.
1855
+ *
1856
+ * @param node - The current AST node being inspected.
1857
+ * @param conditional - True when `node` is inside a conditional execution path.
1858
+ * @param state - Shared walk state (signal registries + violation accumulator).
1859
+ */ function walk(node, conditional, state) {
1860
+ if (!node || typeof node.type !== 'string') {
1861
+ return;
1862
+ }
1863
+ // Stop at nested functions. The caller passes the computed callback's body
1864
+ // (not the function node itself) as the entry point, so any function node
1865
+ // we encounter while walking children is a nested function whose interior
1866
+ // is not synchronously tracked by Angular.
1867
+ if (isFunctionNode(node)) {
1868
+ return;
1869
+ }
1870
+ if (conditional && node.type === 'CallExpression' && Array.isArray(node.arguments) && node.arguments.length === 0) {
1871
+ var match = classifySignalRead(node.callee, state);
1872
+ if (match) {
1873
+ state.violations.push({
1874
+ node: node,
1875
+ signalName: match.name,
1876
+ source: match.source
1877
+ });
1878
+ }
1879
+ }
1880
+ walkChildren(node, conditional, state);
1881
+ }
1882
+ /**
1883
+ * Returns the signal-name match for `callee` when it reads a known signal:
1884
+ * either a `this.<name>` access where `<name>` is a class signal property,
1885
+ * or a bare `<name>` identifier where `<name>` is a module-level signal
1886
+ * `const`. Returns null otherwise.
1887
+ *
1888
+ * Cross-class accesses (`this.someService.someSignal()`), method calls on
1889
+ * locals (`icons.reverse()`), and untracked identifiers (`Math.random()`)
1890
+ * return null: the rule prefers silent under-coverage to false positives.
1891
+ *
1892
+ * @param callee - The CallExpression's callee AST node.
1893
+ * @param state - The shared walk state (used to read the signal registries).
1894
+ * @returns Match details (signal name + source), or null when not a known signal read.
1895
+ */ function classifySignalRead(callee, state) {
1896
+ var _callee_object, _callee_property;
1897
+ var result = null;
1898
+ if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'Identifier') {
1899
+ if (state.moduleSignalNames.has(callee.name)) {
1900
+ result = {
1901
+ name: callee.name,
1902
+ source: 'module'
1903
+ };
1904
+ }
1905
+ } else if ((callee === null || callee === void 0 ? void 0 : callee.type) === 'MemberExpression' && callee.computed === false && ((_callee_object = callee.object) === null || _callee_object === void 0 ? void 0 : _callee_object.type) === 'ThisExpression' && ((_callee_property = callee.property) === null || _callee_property === void 0 ? void 0 : _callee_property.type) === 'Identifier') {
1906
+ var _state_classSignalNames;
1907
+ var propName = callee.property.name;
1908
+ if (((_state_classSignalNames = state.classSignalNames) === null || _state_classSignalNames === void 0 ? void 0 : _state_classSignalNames.has(propName)) === true) {
1909
+ result = {
1910
+ name: propName,
1911
+ source: 'this'
1912
+ };
1913
+ }
1914
+ }
1915
+ return result;
1916
+ }
1917
+ /**
1918
+ * Recurses into the appropriate children of `node`, switching `conditional`
1919
+ * to true when descending into a branch that may not execute on every run.
1920
+ *
1921
+ * @param node - The current AST node whose children are walked.
1922
+ * @param conditional - True when `node` itself is in a conditional path.
1923
+ * @param state - Shared walk state.
1924
+ */ function walkChildren(node, conditional, state) {
1925
+ switch(node.type){
1926
+ case 'IfStatement':
1927
+ walk(node.test, conditional, state);
1928
+ walk(node.consequent, true, state);
1929
+ walk(node.alternate, true, state);
1930
+ break;
1931
+ case 'ConditionalExpression':
1932
+ walk(node.test, conditional, state);
1933
+ walk(node.consequent, true, state);
1934
+ walk(node.alternate, true, state);
1935
+ break;
1936
+ case 'LogicalExpression':
1937
+ // `&&`, `||`, and `??` all short-circuit: the right operand only runs
1938
+ // when the left operand triggers the corresponding short-circuit miss.
1939
+ walk(node.left, conditional, state);
1940
+ walk(node.right, true, state);
1941
+ break;
1942
+ case 'SwitchStatement':
1943
+ var _node_cases;
1944
+ walk(node.discriminant, conditional, state);
1945
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
1946
+ try {
1947
+ for(var _iterator = ((_node_cases = node.cases) !== null && _node_cases !== void 0 ? _node_cases : [])[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
1948
+ var switchCase = _step.value;
1949
+ var _switchCase_consequent;
1950
+ walk(switchCase.test, conditional, state);
1951
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
1952
+ try {
1953
+ for(var _iterator1 = ((_switchCase_consequent = switchCase.consequent) !== null && _switchCase_consequent !== void 0 ? _switchCase_consequent : [])[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
1954
+ var statement = _step1.value;
1955
+ walk(statement, true, state);
1956
+ }
1957
+ } catch (err) {
1958
+ _didIteratorError1 = true;
1959
+ _iteratorError1 = err;
1960
+ } finally{
1961
+ try {
1962
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
1963
+ _iterator1.return();
1964
+ }
1965
+ } finally{
1966
+ if (_didIteratorError1) {
1967
+ throw _iteratorError1;
1968
+ }
1969
+ }
1970
+ }
1971
+ }
1972
+ } catch (err) {
1973
+ _didIteratorError = true;
1974
+ _iteratorError = err;
1975
+ } finally{
1976
+ try {
1977
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
1978
+ _iterator.return();
1979
+ }
1980
+ } finally{
1981
+ if (_didIteratorError) {
1982
+ throw _iteratorError;
1983
+ }
1984
+ }
1985
+ }
1986
+ break;
1987
+ case 'ForStatement':
1988
+ walk(node.init, conditional, state);
1989
+ walk(node.test, conditional, state);
1990
+ walk(node.update, true, state);
1991
+ walk(node.body, true, state);
1992
+ break;
1993
+ case 'ForInStatement':
1994
+ case 'ForOfStatement':
1995
+ walk(node.left, conditional, state);
1996
+ walk(node.right, conditional, state);
1997
+ walk(node.body, true, state);
1998
+ break;
1999
+ case 'WhileStatement':
2000
+ case 'DoWhileStatement':
2001
+ walk(node.test, conditional, state);
2002
+ walk(node.body, true, state);
2003
+ break;
2004
+ case 'TryStatement':
2005
+ walk(node.block, conditional, state);
2006
+ walk(node.handler, true, state);
2007
+ walk(node.finalizer, conditional, state);
2008
+ break;
2009
+ default:
2010
+ walkGenericChildren(node, conditional, state);
2011
+ }
2012
+ }
2013
+ /**
2014
+ * Generic AST-walking fallback used for node types that do not introduce
2015
+ * conditional execution. Recurses into every object-valued or array-valued
2016
+ * property whose value looks like an AST node.
2017
+ *
2018
+ * @param node - The current AST node.
2019
+ * @param conditional - True when `node` itself is in a conditional path.
2020
+ * @param state - Shared walk state.
2021
+ */ function walkGenericChildren(node, conditional, state) {
2022
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
2023
+ try {
2024
+ for(var _iterator = Object.keys(node)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
2025
+ var key = _step.value;
2026
+ if (key === 'parent' || key === 'loc' || key === 'range' || key === 'start' || key === 'end') {
2027
+ continue;
2028
+ }
2029
+ var child = node[key];
2030
+ if (Array.isArray(child)) {
2031
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
2032
+ try {
2033
+ for(var _iterator1 = child[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
2034
+ var entry = _step1.value;
2035
+ walk(entry, conditional, state);
2036
+ }
2037
+ } catch (err) {
2038
+ _didIteratorError1 = true;
2039
+ _iteratorError1 = err;
2040
+ } finally{
2041
+ try {
2042
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
2043
+ _iterator1.return();
2044
+ }
2045
+ } finally{
2046
+ if (_didIteratorError1) {
2047
+ throw _iteratorError1;
2048
+ }
2049
+ }
2050
+ }
2051
+ } else if (child && (typeof child === "undefined" ? "undefined" : _type_of(child)) === 'object' && typeof child.type === 'string') {
2052
+ walk(child, conditional, state);
2053
+ }
2054
+ }
2055
+ } catch (err) {
2056
+ _didIteratorError = true;
2057
+ _iteratorError = err;
2058
+ } finally{
2059
+ try {
2060
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
2061
+ _iterator.return();
2062
+ }
2063
+ } finally{
2064
+ if (_didIteratorError) {
2065
+ throw _iteratorError;
2066
+ }
2067
+ }
2068
+ }
2069
+ }
2070
+ /**
2071
+ * Emits one diagnostic per accumulated violation. The first emitted report
2072
+ * carries a combined autofix that hoists each flagged signal read to the top
2073
+ * of the callback body and replaces every flagged call with the hoisted
2074
+ * local. Subsequent reports do not carry a fix because `--fix` will already
2075
+ * have rewritten them via the first report's fix.
2076
+ *
2077
+ * @param callback - The Function/ArrowFunction AST node passed to `computed(...)`.
2078
+ * @param violations - Conditional signal reads collected during the walk.
2079
+ * @param context - The ESLint rule context.
2080
+ */ function reportViolations(callback, violations, context) {
2081
+ var _context_sourceCode;
2082
+ var _context_getSourceCode;
2083
+ var sourceCode = (_context_sourceCode = context.sourceCode) !== null && _context_sourceCode !== void 0 ? _context_sourceCode : (_context_getSourceCode = context.getSourceCode) === null || _context_getSourceCode === void 0 ? void 0 : _context_getSourceCode.call(context);
2084
+ var plan = buildFixPlan(callback, violations);
2085
+ violations.forEach(function(violation, index) {
2086
+ context.report({
2087
+ node: violation.node,
2088
+ messageId: 'conditionalSignalRead',
2089
+ data: {
2090
+ call: getCallPreview(violation.node, context)
2091
+ },
2092
+ fix: index === 0 && plan ? function(fixer) {
2093
+ return applyFixPlan(fixer, plan, sourceCode);
2094
+ } : undefined
2095
+ });
2096
+ });
2097
+ }
2098
+ /**
2099
+ * Plans the hoisting transformation for a callback whose violations have
2100
+ * been collected. Returns null when no hoist can be planned (e.g. every
2101
+ * candidate name would shadow an existing binding).
2102
+ *
2103
+ * @param callback - The Function/ArrowFunction AST node passed to `computed(...)`.
2104
+ * @param violations - Conditional signal reads collected during the walk.
2105
+ * @param sourceCode - The ESLint sourceCode service.
2106
+ * @returns A fix plan, or null when no hoist is possible.
2107
+ */ function buildFixPlan(callback, violations, sourceCode) {
2108
+ var body = callback === null || callback === void 0 ? void 0 : callback.body;
2109
+ var result = null;
2110
+ if (body) {
2111
+ var existingLocals = collectExistingLocalNames(body);
2112
+ var localNames = new Map();
2113
+ var usedNames = new Set(existingLocals);
2114
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
2115
+ try {
2116
+ for(var _iterator = violations[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
2117
+ var violation = _step.value;
2118
+ if (localNames.has(violation.signalName)) {
2119
+ continue;
2120
+ }
2121
+ var preferred = computeLocalName(violation.signalName);
2122
+ if (usedNames.has(preferred)) {
2123
+ continue;
2124
+ }
2125
+ usedNames.add(preferred);
2126
+ localNames.set(violation.signalName, {
2127
+ name: preferred,
2128
+ source: violation.source
2129
+ });
2130
+ }
2131
+ } catch (err) {
2132
+ _didIteratorError = true;
2133
+ _iteratorError = err;
2134
+ } finally{
2135
+ try {
2136
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
2137
+ _iterator.return();
2138
+ }
2139
+ } finally{
2140
+ if (_didIteratorError) {
2141
+ throw _iteratorError;
2142
+ }
2143
+ }
2144
+ }
2145
+ if (localNames.size > 0) {
2146
+ var replacements = violations.filter(function(violation) {
2147
+ return localNames.has(violation.signalName);
2148
+ });
2149
+ if (replacements.length > 0) {
2150
+ result = {
2151
+ callback: callback,
2152
+ body: body,
2153
+ localNames: localNames,
2154
+ replacements: replacements
2155
+ };
2156
+ }
2157
+ }
2158
+ }
2159
+ return result;
2160
+ }
2161
+ /**
2162
+ * Computes the local variable name for a signal property name. Strips the
2163
+ * trailing `Signal` suffix when present (`xSignal` → `x`, `_configSignal` →
2164
+ * `_config`); otherwise returns the original name unchanged.
2165
+ *
2166
+ * A name that is purely `'Signal'` is returned as-is rather than stripped
2167
+ * to an empty string.
2168
+ *
2169
+ * @param signalName - The signal property name to derive the local from.
2170
+ * @returns The local variable name.
2171
+ */ function computeLocalName(signalName) {
2172
+ var suffix = 'Signal';
2173
+ var result = signalName;
2174
+ if (signalName.length > suffix.length && signalName.endsWith(suffix)) {
2175
+ result = signalName.slice(0, signalName.length - suffix.length);
2176
+ }
2177
+ return result;
2178
+ }
2179
+ /**
2180
+ * Collects all identifier names introduced by `VariableDeclarator`s within
2181
+ * the function body's top-level statements (the only locations a hoist
2182
+ * could possibly shadow). Nested function bodies and inner block scopes are
2183
+ * not inspected — shadowing those is harmless.
2184
+ *
2185
+ * Also accepts expression-body callbacks: an expression body cannot declare
2186
+ * locals, so it contributes no names.
2187
+ *
2188
+ * @param body - The callback's BlockStatement or expression AST node.
2189
+ * @returns Identifier names already declared in the body's scope.
2190
+ */ function collectExistingLocalNames(body) {
2191
+ var names = new Set();
2192
+ if ((body === null || body === void 0 ? void 0 : body.type) === 'BlockStatement') {
2193
+ var _body_body;
2194
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
2195
+ try {
2196
+ for(var _iterator = ((_body_body = body.body) !== null && _body_body !== void 0 ? _body_body : [])[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
2197
+ var statement = _step.value;
2198
+ if ((statement === null || statement === void 0 ? void 0 : statement.type) === 'VariableDeclaration') {
2199
+ var _statement_declarations;
2200
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
2201
+ try {
2202
+ for(var _iterator1 = ((_statement_declarations = statement.declarations) !== null && _statement_declarations !== void 0 ? _statement_declarations : [])[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
2203
+ var declarator = _step1.value;
2204
+ collectPatternNames(declarator === null || declarator === void 0 ? void 0 : declarator.id, names);
2205
+ }
2206
+ } catch (err) {
2207
+ _didIteratorError1 = true;
2208
+ _iteratorError1 = err;
2209
+ } finally{
2210
+ try {
2211
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
2212
+ _iterator1.return();
2213
+ }
2214
+ } finally{
2215
+ if (_didIteratorError1) {
2216
+ throw _iteratorError1;
2217
+ }
2218
+ }
2219
+ }
2220
+ }
2221
+ }
2222
+ } catch (err) {
2223
+ _didIteratorError = true;
2224
+ _iteratorError = err;
2225
+ } finally{
2226
+ try {
2227
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
2228
+ _iterator.return();
2229
+ }
2230
+ } finally{
2231
+ if (_didIteratorError) {
2232
+ throw _iteratorError;
2233
+ }
2234
+ }
2235
+ }
2236
+ }
2237
+ return names;
2238
+ }
2239
+ /**
2240
+ * Recursively collects identifier names introduced by a destructuring
2241
+ * pattern (or a plain Identifier). Handles ObjectPattern, ArrayPattern,
2242
+ * RestElement, and AssignmentPattern; ignores other shapes.
2243
+ *
2244
+ * @param pattern - The Identifier or destructuring pattern AST node.
2245
+ * @param names - The set that is mutated with discovered names.
2246
+ */ function collectPatternNames(pattern, names) {
2247
+ if (!pattern || typeof pattern.type !== 'string') {
2248
+ return;
2249
+ }
2250
+ switch(pattern.type){
2251
+ case 'Identifier':
2252
+ names.add(pattern.name);
2253
+ break;
2254
+ case 'ObjectPattern':
2255
+ var _pattern_properties;
2256
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
2257
+ try {
2258
+ for(var _iterator = ((_pattern_properties = pattern.properties) !== null && _pattern_properties !== void 0 ? _pattern_properties : [])[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
2259
+ var property = _step.value;
2260
+ if ((property === null || property === void 0 ? void 0 : property.type) === 'Property') {
2261
+ collectPatternNames(property.value, names);
2262
+ } else if ((property === null || property === void 0 ? void 0 : property.type) === 'RestElement') {
2263
+ collectPatternNames(property.argument, names);
2264
+ }
2265
+ }
2266
+ } catch (err) {
2267
+ _didIteratorError = true;
2268
+ _iteratorError = err;
2269
+ } finally{
2270
+ try {
2271
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
2272
+ _iterator.return();
2273
+ }
2274
+ } finally{
2275
+ if (_didIteratorError) {
2276
+ throw _iteratorError;
2277
+ }
2278
+ }
2279
+ }
2280
+ break;
2281
+ case 'ArrayPattern':
2282
+ var _pattern_elements;
2283
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
2284
+ try {
2285
+ for(var _iterator1 = ((_pattern_elements = pattern.elements) !== null && _pattern_elements !== void 0 ? _pattern_elements : [])[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
2286
+ var element = _step1.value;
2287
+ collectPatternNames(element, names);
2288
+ }
2289
+ } catch (err) {
2290
+ _didIteratorError1 = true;
2291
+ _iteratorError1 = err;
2292
+ } finally{
2293
+ try {
2294
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
2295
+ _iterator1.return();
2296
+ }
2297
+ } finally{
2298
+ if (_didIteratorError1) {
2299
+ throw _iteratorError1;
2300
+ }
2301
+ }
2302
+ }
2303
+ break;
2304
+ case 'RestElement':
2305
+ collectPatternNames(pattern.argument, names);
2306
+ break;
2307
+ case 'AssignmentPattern':
2308
+ collectPatternNames(pattern.left, names);
2309
+ break;
2310
+ }
2311
+ }
2312
+ /**
2313
+ * Applies a fix plan, returning the list of fixer operations that hoist each
2314
+ * planned signal read and replace its flagged call sites.
2315
+ *
2316
+ * For BlockStatement bodies the hoists are inserted before the first
2317
+ * statement (or after the opening brace when the body is empty). For
2318
+ * expression-body callbacks the entire body expression is replaced with a
2319
+ * BlockStatement that contains the hoists followed by a `return <expr>;`.
2320
+ *
2321
+ * @param fixer - The ESLint RuleFixer.
2322
+ * @param plan - The fix plan to apply.
2323
+ * @param sourceCode - The ESLint sourceCode service.
2324
+ * @returns The list of fixer operations.
2325
+ */ function applyFixPlan(fixer, plan, sourceCode) {
2326
+ var fixes = [];
2327
+ var hoistLines = [];
2328
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
2329
+ try {
2330
+ for(var _iterator = plan.localNames[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
2331
+ var _step_value = _sliced_to_array(_step.value, 2), signalName = _step_value[0], local = _step_value[1];
2332
+ var rhs = local.source === 'this' ? "this.".concat(signalName, "()") : "".concat(signalName, "()");
2333
+ hoistLines.push("const ".concat(local.name, " = ").concat(rhs, ";"));
2334
+ }
2335
+ } catch (err) {
2336
+ _didIteratorError = true;
2337
+ _iteratorError = err;
2338
+ } finally{
2339
+ try {
2340
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
2341
+ _iterator.return();
2342
+ }
2343
+ } finally{
2344
+ if (_didIteratorError) {
2345
+ throw _iteratorError;
2346
+ }
2347
+ }
2348
+ }
2349
+ if (plan.body.type === 'BlockStatement') {
2350
+ var _plan_body_body;
2351
+ var indent = getBlockIndent(plan.body, sourceCode);
2352
+ var firstStatement = (_plan_body_body = plan.body.body) === null || _plan_body_body === void 0 ? void 0 : _plan_body_body[0];
2353
+ if (firstStatement) {
2354
+ // `insertTextBefore(firstStatement, …)` inserts at the position of the
2355
+ // first non-whitespace character of the statement; the existing line
2356
+ // already carries the leading indent up to that position. So the first
2357
+ // hoist line is emitted WITHOUT a leading indent (it reuses the
2358
+ // existing one), every subsequent line carries the full indent, and
2359
+ // the trailing `\n${indent}` re-establishes the indent for the
2360
+ // original statement that follows.
2361
+ var hoistText = hoistLines.map(function(line, index) {
2362
+ return index === 0 ? line : "".concat(indent).concat(line);
2363
+ }).join('\n');
2364
+ fixes.push(fixer.insertTextBefore(firstStatement, "".concat(hoistText, "\n").concat(indent)));
2365
+ } else {
2366
+ var openBrace = sourceCode.getFirstToken(plan.body);
2367
+ if (openBrace) {
2368
+ var hoistText1 = hoistLines.map(function(line) {
2369
+ return "".concat(indent).concat(line);
2370
+ }).join('\n');
2371
+ fixes.push(fixer.insertTextAfter(openBrace, "\n".concat(hoistText1, "\n")));
2372
+ }
2373
+ }
2374
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
2375
+ try {
2376
+ for(var _iterator1 = plan.replacements[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
2377
+ var replacement = _step1.value;
2378
+ var local1 = plan.localNames.get(replacement.signalName);
2379
+ if (local1) {
2380
+ fixes.push(fixer.replaceText(replacement.node, local1.name));
2381
+ }
2382
+ }
2383
+ } catch (err) {
2384
+ _didIteratorError1 = true;
2385
+ _iteratorError1 = err;
2386
+ } finally{
2387
+ try {
2388
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
2389
+ _iterator1.return();
2390
+ }
2391
+ } finally{
2392
+ if (_didIteratorError1) {
2393
+ throw _iteratorError1;
2394
+ }
2395
+ }
2396
+ }
2397
+ } else {
2398
+ // Expression body: rewrite `(arg) => <expr>` to
2399
+ // `(arg) => { …hoists; return <expr-with-substitutions>; }`. The body
2400
+ // replacement covers every flagged call site, so per-call replacements
2401
+ // are baked into the substituted expression text rather than emitted as
2402
+ // separate fixer operations (which would overlap the body replacement).
2403
+ var baseIndent = getStatementIndent(plan.callback, sourceCode);
2404
+ var innerIndent = "".concat(baseIndent, " ");
2405
+ var hoistText2 = hoistLines.map(function(line) {
2406
+ return "".concat(innerIndent).concat(line);
2407
+ }).join('\n');
2408
+ var substitutedExpr = substituteInExpression(plan, sourceCode);
2409
+ fixes.push(fixer.replaceText(plan.body, "{\n".concat(hoistText2, "\n").concat(innerIndent, "return ").concat(substitutedExpr, ";\n").concat(baseIndent, "}")));
2410
+ }
2411
+ return fixes;
2412
+ }
2413
+ /**
2414
+ * Returns the leading-whitespace indent of the line that contains the start
2415
+ * of `node`. Used to align the inserted block body with the surrounding
2416
+ * statement (e.g. the `readonly fooSignal = computed(() => …)` line).
2417
+ *
2418
+ * Falls back to an empty string when the node has no detectable line indent.
2419
+ *
2420
+ * @param node - The AST node whose enclosing line indent is wanted.
2421
+ * @param sourceCode - The ESLint sourceCode service.
2422
+ * @returns The indent string (spaces / tabs), or `''` when none can be derived.
2423
+ */ function getStatementIndent(node, sourceCode) {
2424
+ var _ref, _sourceCode_text;
2425
+ var _sourceCode_getText;
2426
+ var source = (_ref = (_sourceCode_text = sourceCode.text) !== null && _sourceCode_text !== void 0 ? _sourceCode_text : (_sourceCode_getText = sourceCode.getText) === null || _sourceCode_getText === void 0 ? void 0 : _sourceCode_getText.call(sourceCode)) !== null && _ref !== void 0 ? _ref : '';
2427
+ var start = node.range[0];
2428
+ var lineStart = start;
2429
+ var result = '';
2430
+ while(lineStart > 0 && source[lineStart - 1] !== '\n'){
2431
+ lineStart -= 1;
2432
+ }
2433
+ var slice = source.slice(lineStart, start);
2434
+ var match = /^[ \t]*/.exec(slice);
2435
+ if (match) {
2436
+ result = match[0];
2437
+ }
2438
+ return result;
2439
+ }
2440
+ /**
2441
+ * Builds the substituted text for an expression-body callback by walking the
2442
+ * raw source text and swapping each flagged call's source range for its
2443
+ * hoisted local name. Replacements are applied right-to-left so earlier
2444
+ * ranges stay valid as the string is mutated.
2445
+ *
2446
+ * @param plan - The fix plan whose body is an expression.
2447
+ * @param sourceCode - The ESLint sourceCode service.
2448
+ * @returns The expression text with every flagged call site replaced.
2449
+ */ function substituteInExpression(plan, sourceCode) {
2450
+ var bodyStart = plan.body.range[0];
2451
+ var ordered = _to_consumable_array(plan.replacements).sort(function(a, b) {
2452
+ return b.node.range[0] - a.node.range[0];
2453
+ });
2454
+ var result = sourceCode.getText(plan.body);
2455
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
2456
+ try {
2457
+ for(var _iterator = ordered[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
2458
+ var replacement = _step.value;
2459
+ var local = plan.localNames.get(replacement.signalName);
2460
+ if (local) {
2461
+ var start = replacement.node.range[0] - bodyStart;
2462
+ var end = replacement.node.range[1] - bodyStart;
2463
+ result = result.slice(0, start) + local.name + result.slice(end);
2464
+ }
2465
+ }
2466
+ } catch (err) {
2467
+ _didIteratorError = true;
2468
+ _iteratorError = err;
2469
+ } finally{
2470
+ try {
2471
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
2472
+ _iterator.return();
2473
+ }
2474
+ } finally{
2475
+ if (_didIteratorError) {
2476
+ throw _iteratorError;
2477
+ }
2478
+ }
2479
+ }
2480
+ return result;
2481
+ }
2482
+ /**
2483
+ * Returns the indent string (whitespace before the first statement) for a
2484
+ * BlockStatement, falling back to a sensible default (`' '`) when the
2485
+ * block contains no statements or no leading whitespace can be located.
2486
+ *
2487
+ * @param block - The BlockStatement AST node.
2488
+ * @param sourceCode - The ESLint sourceCode service.
2489
+ * @returns The indent string to use for inserted hoist lines.
2490
+ */ function getBlockIndent(block, sourceCode) {
2491
+ var _block_body;
2492
+ var firstStatement = (_block_body = block.body) === null || _block_body === void 0 ? void 0 : _block_body[0];
2493
+ var result = ' ';
2494
+ if (firstStatement) {
2495
+ var _ref, _sourceCode_text;
2496
+ var _sourceCode_getText;
2497
+ var source = (_ref = (_sourceCode_text = sourceCode.text) !== null && _sourceCode_text !== void 0 ? _sourceCode_text : (_sourceCode_getText = sourceCode.getText) === null || _sourceCode_getText === void 0 ? void 0 : _sourceCode_getText.call(sourceCode)) !== null && _ref !== void 0 ? _ref : '';
2498
+ var textBefore = source.slice(block.range[0], firstStatement.range[0]);
2499
+ var match = /(?:^|\n)([ \t]*)$/.exec(textBefore);
2500
+ if (match) {
2501
+ result = match[1];
2502
+ }
2503
+ }
2504
+ return result;
2505
+ }
2506
+ /**
2507
+ * Returns a short, human-readable textual preview of the call expression,
2508
+ * suitable for inclusion in the diagnostic message. Falls back to `'signal'`
2509
+ * when the source code is unavailable.
2510
+ *
2511
+ * @param node - The CallExpression AST node.
2512
+ * @param context - The ESLint rule context (used to access `sourceCode`).
2513
+ * @returns A truncated textual preview of the call.
2514
+ */ function getCallPreview(node, context) {
2515
+ var _context_sourceCode;
2516
+ var _context_getSourceCode;
2517
+ var sourceCode = (_context_sourceCode = context.sourceCode) !== null && _context_sourceCode !== void 0 ? _context_sourceCode : (_context_getSourceCode = context.getSourceCode) === null || _context_getSourceCode === void 0 ? void 0 : _context_getSourceCode.call(context);
2518
+ var preview = 'signal';
2519
+ if (sourceCode && node.callee) {
2520
+ var calleeText = sourceCode.getText(node.callee);
2521
+ preview = "".concat(calleeText, "()");
2522
+ if (preview.length > CALL_PREVIEW_MAX_LENGTH) {
2523
+ preview = "".concat(preview.slice(0, CALL_PREVIEW_MAX_LENGTH - 3), "...");
2524
+ }
2525
+ }
2526
+ return preview;
2527
+ }
2528
+
2529
+ /**
2530
+ * ESLint plugin for dbx-web rules.
2531
+ *
2532
+ * Register as a plugin in your flat ESLint config, then enable individual rules
2533
+ * under the chosen plugin prefix (e.g. 'dereekb-dbx-web/require-clean-subscription').
2534
+ */ var DBX_WEB_ESLINT_PLUGIN = {
2535
+ rules: {
2536
+ 'require-clean-subscription': DBX_WEB_REQUIRE_CLEAN_SUBSCRIPTION_RULE,
2537
+ 'require-complete-on-destroy': DBX_WEB_REQUIRE_COMPLETE_ON_DESTROY_RULE,
2538
+ 'no-redundant-on-destroy': DBX_WEB_NO_REDUNDANT_ON_DESTROY_RULE,
2539
+ 'require-computed-signal-suffix': DBX_WEB_REQUIRE_COMPUTED_SIGNAL_SUFFIX_RULE,
2540
+ 'require-component-config-input': DBX_WEB_REQUIRE_COMPONENT_CONFIG_INPUT_RULE,
2541
+ 'require-top-level-computed-signals': DBX_WEB_REQUIRE_TOP_LEVEL_COMPUTED_SIGNALS_RULE
2542
+ }
2543
+ };
2544
+ /**
2545
+ * camelCase alias of {@link DBX_WEB_ESLINT_PLUGIN} matching the conventional ESLint plugin export name.
2546
+ *
2547
+ * @dbxAllowConstantName
2548
+ */ var dbxWebESLintPlugin = DBX_WEB_ESLINT_PLUGIN;
1098
2549
 
1099
- exports.dbxWebEslintPlugin = dbxWebEslintPlugin;
1100
- exports.dbxWebNoRedundantOnDestroyRule = dbxWebNoRedundantOnDestroyRule;
1101
- exports.dbxWebRequireCleanSubscriptionRule = dbxWebRequireCleanSubscriptionRule;
1102
- exports.dbxWebRequireCompleteOnDestroyRule = dbxWebRequireCompleteOnDestroyRule;
2550
+ exports.DBX_WEB_ESLINT_PLUGIN = DBX_WEB_ESLINT_PLUGIN;
2551
+ exports.DBX_WEB_NO_REDUNDANT_ON_DESTROY_RULE = DBX_WEB_NO_REDUNDANT_ON_DESTROY_RULE;
2552
+ exports.DBX_WEB_REQUIRE_CLEAN_SUBSCRIPTION_RULE = DBX_WEB_REQUIRE_CLEAN_SUBSCRIPTION_RULE;
2553
+ exports.DBX_WEB_REQUIRE_COMPLETE_ON_DESTROY_RULE = DBX_WEB_REQUIRE_COMPLETE_ON_DESTROY_RULE;
2554
+ exports.DBX_WEB_REQUIRE_COMPONENT_CONFIG_INPUT_RULE = DBX_WEB_REQUIRE_COMPONENT_CONFIG_INPUT_RULE;
2555
+ exports.DBX_WEB_REQUIRE_COMPUTED_SIGNAL_SUFFIX_RULE = DBX_WEB_REQUIRE_COMPUTED_SIGNAL_SUFFIX_RULE;
2556
+ exports.DBX_WEB_REQUIRE_TOP_LEVEL_COMPUTED_SIGNALS_RULE = DBX_WEB_REQUIRE_TOP_LEVEL_COMPUTED_SIGNALS_RULE;
2557
+ exports.dbxWebESLintPlugin = dbxWebESLintPlugin;