@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/ANGULAR_SIGNALS.md +174 -16
- package/UPGRADING_V19.md +3 -0
- package/dsivd-prestations-ng-19.0.6-beta.2.tgz +0 -0
- package/eslint/rules/__tests__/no-direct-signal-mutation.test.mjs +4 -4
- package/eslint/rules/no-direct-signal-mutation.mjs +136 -13
- package/fesm2022/dsivd-prestations-ng.mjs +11 -11
- package/fesm2022/dsivd-prestations-ng.mjs.map +1 -1
- package/package.json +1 -1
- package/src/eslint/rules/__tests__/no-direct-signal-mutation.test.mjs +4 -4
- package/src/eslint/rules/no-direct-signal-mutation.mjs +136 -13
- package/types/dsivd-prestations-ng.d.ts +5 -6
- package/dsivd-prestations-ng-19.0.5.tgz +0 -0
package/package.json
CHANGED
|
@@ -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
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
*
|
|
39
|
-
*
|
|
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
|
|
42
|
-
if (!node)
|
|
43
|
-
|
|
44
|
-
return true;
|
|
98
|
+
function getFirstCallExpression(node) {
|
|
99
|
+
if (!node) {
|
|
100
|
+
return null;
|
|
45
101
|
}
|
|
46
102
|
if (node.type === "MemberExpression") {
|
|
47
|
-
return
|
|
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.
|
|
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"; "
|
|
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
|