@dsivd/prestations-ng 19.0.5 → 19.0.6-beta.2

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/package.json CHANGED
@@ -40,7 +40,7 @@
40
40
  "ng-update": {
41
41
  "migrations": "./schematics/migration-collection.json"
42
42
  },
43
- "version": "19.0.5",
43
+ "version": "19.0.6-beta.2",
44
44
  "module": "fesm2022/dsivd-prestations-ng.mjs",
45
45
  "typings": "types/dsivd-prestations-ng.d.ts",
46
46
  "exports": {
@@ -42,6 +42,10 @@ describe('no-direct-signal-mutation', () => {
42
42
  'let x = value',
43
43
  'const obj = { prop: value }',
44
44
  'array[0] = value',
45
+ 'pendingFilesByFormKey.get(formKey).files = []',
46
+ 'this.pendingFilesByFormKey.get(formKey).url = baseUrl',
47
+ 'const autocompleteComponent = viewChild("auto"); autocompleteComponent().inputElement().nativeElement.hidden = true',
48
+ 'component.inputElement().nativeElement.value = ""',
45
49
 
46
50
  // Method chaining without assignment
47
51
  'model().update((v) => v).subscribe()',
@@ -72,10 +76,6 @@ describe('no-direct-signal-mutation', () => {
72
76
  code: "this.model().deeply.nested.property = value",
73
77
  errors: [{ messageId: 'directMutation' }],
74
78
  },
75
- {
76
- code: "obj.getter().property = value",
77
- errors: [{ messageId: 'directMutation' }],
78
- },
79
79
  {
80
80
  code: "model().property += value",
81
81
  errors: [{ messageId: 'directMutation' }],
@@ -19,13 +19,23 @@ export default {
19
19
  },
20
20
 
21
21
  create(context) {
22
+ const sourceCode = context.sourceCode;
23
+ const viewQueryFactoryNames = new Set([
24
+ "viewChild",
25
+ "viewChildren",
26
+ "contentChild",
27
+ "contentChildren",
28
+ ]);
29
+ const viewQueryAccessorNames = collectViewQueryAccessorNames(sourceCode.ast);
30
+
22
31
  return {
23
32
  AssignmentExpression(node) {
24
- // Check if left side is a member expression with a call expression
25
- if (
26
- node.left.type === "MemberExpression" &&
27
- isCallExpression(node.left.object)
28
- ) {
33
+ if (node.left.type !== "MemberExpression") {
34
+ return;
35
+ }
36
+
37
+ const rootCallExpression = getFirstCallExpression(node.left.object);
38
+ if (rootCallExpression && isSignalLikeRootCall(rootCallExpression)) {
29
39
  context.report({
30
40
  node,
31
41
  messageId: "directMutation",
@@ -34,19 +44,132 @@ export default {
34
44
  },
35
45
  };
36
46
 
47
+ function isSignalLikeRootCall(node) {
48
+ if (node.arguments.length !== 0) {
49
+ return false;
50
+ }
51
+
52
+ // model().prop = ... (local signal-like accessor)
53
+ if (node.callee.type === "Identifier") {
54
+ return !viewQueryAccessorNames.has(node.callee.name);
55
+ }
56
+
57
+ // this.model().prop = ... (component/service signal accessor)
58
+ // component.model().prop = ... should not be assumed to be a signal read.
59
+ if (node.callee.type === "MemberExpression") {
60
+ if (node.callee.object?.type !== "ThisExpression") {
61
+ return false;
62
+ }
63
+ return !isViewQueryAccessorCall(node.callee);
64
+ }
65
+
66
+ return false;
67
+ }
68
+
69
+ function isViewQueryAccessorCall(callee) {
70
+ const calledName = getCalledName(callee);
71
+ return calledName ? viewQueryAccessorNames.has(calledName) : false;
72
+ }
73
+
74
+ function getCalledName(callee) {
75
+ if (!callee) {
76
+ return null;
77
+ }
78
+ if (callee.type === "Identifier") {
79
+ return callee.name;
80
+ }
81
+ if (
82
+ callee.type === "MemberExpression" &&
83
+ !callee.computed &&
84
+ callee.property.type === "Identifier"
85
+ ) {
86
+ return callee.property.name;
87
+ }
88
+ return null;
89
+ }
90
+
37
91
  /**
38
- * Recursively check if a node is a call expression.
39
- * Handles: func().prop, obj.func().prop, this.func().prop, etc.
92
+ * Finds the first/root function call used as the base of a mutation chain.
93
+ * Examples:
94
+ * - model().property = x => model()
95
+ * - this.model().deep.prop = x => this.model()
96
+ * - this.query().el().x = y => this.query()
40
97
  */
41
- function isCallExpression(node) {
42
- if (!node) return false;
43
- if (node.type === "CallExpression") {
44
- return true;
98
+ function getFirstCallExpression(node) {
99
+ if (!node) {
100
+ return null;
45
101
  }
46
102
  if (node.type === "MemberExpression") {
47
- return isCallExpression(node.object);
103
+ return getFirstCallExpression(node.object);
104
+ }
105
+ if (node.type === "CallExpression") {
106
+ if (node.callee.type === "MemberExpression") {
107
+ const nestedCall = getFirstCallExpression(node.callee.object);
108
+ return nestedCall || node;
109
+ }
110
+ return node;
111
+ }
112
+ return null;
113
+ }
114
+
115
+ function collectViewQueryAccessorNames(ast) {
116
+ const names = new Set();
117
+
118
+ walkAst(ast, (node) => {
119
+ if (
120
+ node.type === "VariableDeclarator" &&
121
+ node.id?.type === "Identifier" &&
122
+ isViewQueryFactoryCall(node.init)
123
+ ) {
124
+ names.add(node.id.name);
125
+ return;
126
+ }
127
+
128
+ if (
129
+ (node.type === "PropertyDefinition" || node.type === "ClassProperty") &&
130
+ node.key?.type === "Identifier" &&
131
+ isViewQueryFactoryCall(node.value)
132
+ ) {
133
+ names.add(node.key.name);
134
+ }
135
+ });
136
+
137
+ return names;
138
+ }
139
+
140
+ function isViewQueryFactoryCall(node) {
141
+ return (
142
+ node?.type === "CallExpression" &&
143
+ node.callee?.type === "Identifier" &&
144
+ viewQueryFactoryNames.has(node.callee.name)
145
+ );
146
+ }
147
+
148
+ function walkAst(node, visitor, visited = new WeakSet()) {
149
+ if (!node || typeof node !== "object") {
150
+ return;
151
+ }
152
+ if (visited.has(node)) {
153
+ return;
154
+ }
155
+ visited.add(node);
156
+
157
+ visitor(node);
158
+
159
+ for (const key of Object.keys(node)) {
160
+ // ESLint AST nodes include cyclic parent references.
161
+ if (key === "parent") {
162
+ continue;
163
+ }
164
+ const child = node[key];
165
+ if (Array.isArray(child)) {
166
+ for (const element of child) {
167
+ walkAst(element, visitor, visited);
168
+ }
169
+ } else {
170
+ walkAst(child, visitor, visited);
171
+ }
48
172
  }
49
- return false;
50
173
  }
51
174
  },
52
175
  };
@@ -1889,7 +1889,7 @@ declare class ComponentError {
1889
1889
  }
1890
1890
 
1891
1891
  declare class FoehnValidationAlertsComponent {
1892
- component: _angular_core.InputSignal<FoehnInputComponent<any>>;
1892
+ readonly component: _angular_core.InputSignal<FoehnInputComponent<any>>;
1893
1893
  readonly shouldErrorsBeLive: _angular_core.InputSignal<boolean>;
1894
1894
  private validationHandlerService;
1895
1895
  get errorMessages(): FormError[];
@@ -2088,7 +2088,7 @@ declare class FoehnListComponent implements OnChanges {
2088
2088
  readonly previousLabel: _angular_core.InputSignal<string>;
2089
2089
  readonly nextLabel: _angular_core.InputSignal<string>;
2090
2090
  readonly trackByFn: _angular_core.InputSignal<(index: number, _item: unknown) => number>;
2091
- templateVariable: _angular_core.Signal<TemplateRef<any>>;
2091
+ readonly templateVariable: _angular_core.Signal<TemplateRef<any>>;
2092
2092
  readonly pageChange: _angular_core.OutputEmitterRef<PageChangeEvent>;
2093
2093
  currentPage: number;
2094
2094
  filteredList: unknown[];
@@ -2137,7 +2137,7 @@ declare class FoehnTableComponent extends FoehnInputComponent<unknown[]> impleme
2137
2137
  readonly columnsConfiguration: _angular_core.ModelSignal<FoehnTableColumnConfiguration<unknown>[]>;
2138
2138
  readonly itemsPerPage: _angular_core.InputSignal<number>;
2139
2139
  readonly fixedPageCount: _angular_core.InputSignal<number>;
2140
- readonly sort: _angular_core.InputSignal<TableSort>;
2140
+ readonly sort: _angular_core.ModelSignal<TableSort>;
2141
2141
  readonly title: _angular_core.InputSignal<string>;
2142
2142
  readonly totalElements: _angular_core.InputSignal<number>;
2143
2143
  readonly titleSrOnly: _angular_core.InputSignal<boolean>;
@@ -2145,7 +2145,6 @@ declare class FoehnTableComponent extends FoehnInputComponent<unknown[]> impleme
2145
2145
  readonly nextLabel: _angular_core.InputSignal<string>;
2146
2146
  readonly tableClass: _angular_core.InputSignal<string>;
2147
2147
  readonly trackByFn: _angular_core.InputSignal<TrackByFunction<unknown>>;
2148
- readonly sortChange: _angular_core.OutputEmitterRef<TableSort>;
2149
2148
  readonly pageChange: _angular_core.OutputEmitterRef<FoehnTablePageChangeEvent>;
2150
2149
  readonly rowClick: _angular_core.OutputEmitterRef<RowElement>;
2151
2150
  currentPage: number;
@@ -2164,7 +2163,7 @@ declare class FoehnTableComponent extends FoehnInputComponent<unknown[]> impleme
2164
2163
  manageRowClick(rowElem: HTMLTableRowElement, item: unknown): void;
2165
2164
  private buildFilteredList;
2166
2165
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<FoehnTableComponent, never>;
2167
- static ɵcmp: _angular_core.ɵɵComponentDeclaration<FoehnTableComponent, "foehn-table", never, { "columnsConfiguration": { "alias": "columnsConfiguration"; "required": false; "isSignal": true; }; "itemsPerPage": { "alias": "itemsPerPage"; "required": false; "isSignal": true; }; "fixedPageCount": { "alias": "fixedPageCount"; "required": false; "isSignal": true; }; "sort": { "alias": "sort"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "totalElements": { "alias": "totalElements"; "required": false; "isSignal": true; }; "titleSrOnly": { "alias": "titleSrOnly"; "required": false; "isSignal": true; }; "previousLabel": { "alias": "previousLabel"; "required": false; "isSignal": true; }; "nextLabel": { "alias": "nextLabel"; "required": false; "isSignal": true; }; "tableClass": { "alias": "tableClass"; "required": false; "isSignal": true; }; "trackByFn": { "alias": "trackByFn"; "required": false; "isSignal": true; }; }, { "columnsConfiguration": "columnsConfigurationChange"; "sortChange": "sortChange"; "pageChange": "pageChange"; "rowClick": "rowClick"; }, never, ["*"], true, never>;
2166
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<FoehnTableComponent, "foehn-table", never, { "columnsConfiguration": { "alias": "columnsConfiguration"; "required": false; "isSignal": true; }; "itemsPerPage": { "alias": "itemsPerPage"; "required": false; "isSignal": true; }; "fixedPageCount": { "alias": "fixedPageCount"; "required": false; "isSignal": true; }; "sort": { "alias": "sort"; "required": false; "isSignal": true; }; "title": { "alias": "title"; "required": false; "isSignal": true; }; "totalElements": { "alias": "totalElements"; "required": false; "isSignal": true; }; "titleSrOnly": { "alias": "titleSrOnly"; "required": false; "isSignal": true; }; "previousLabel": { "alias": "previousLabel"; "required": false; "isSignal": true; }; "nextLabel": { "alias": "nextLabel"; "required": false; "isSignal": true; }; "tableClass": { "alias": "tableClass"; "required": false; "isSignal": true; }; "trackByFn": { "alias": "trackByFn"; "required": false; "isSignal": true; }; }, { "columnsConfiguration": "columnsConfigurationChange"; "sort": "sortChange"; "pageChange": "pageChange"; "rowClick": "rowClick"; }, never, ["*"], true, never>;
2168
2167
  }
2169
2168
 
2170
2169
  declare class RegisterNgModelService {
@@ -3660,7 +3659,7 @@ declare class FoehnFeedbackNotificationComponent {
3660
3659
  goodIconName: _fortawesome_fontawesome_common_types.IconDefinition;
3661
3660
  mediumIconName: _fortawesome_fontawesome_common_types.IconDefinition;
3662
3661
  badIconName: _fortawesome_fontawesome_common_types.IconDefinition;
3663
- expanded: _angular_core.WritableSignal<boolean>;
3662
+ readonly expanded: _angular_core.WritableSignal<boolean>;
3664
3663
  isScrollEnded: boolean;
3665
3664
  visible: Observable<boolean>;
3666
3665
  goodUrl: Observable<string>;
Binary file