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

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.
Files changed (39) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/CONTRIBUTING.md +25 -0
  3. package/ESLINT_PLUGIN.md +198 -0
  4. package/README.md +3 -0
  5. package/UPGRADING_V19.md +38 -143
  6. package/dsivd-prestations-ng-19.0.7.tgz +0 -0
  7. package/eslint/configs/template-base.mjs +10 -0
  8. package/eslint/configs/template-recommended.mjs +24 -0
  9. package/eslint/configs/template-rules.mjs +17 -0
  10. package/eslint/configs/ts-base.mjs +10 -0
  11. package/eslint/configs/ts-recommended.mjs +142 -0
  12. package/eslint/configs/ts-rules.mjs +14 -0
  13. package/eslint/index.mjs +20 -5
  14. package/eslint/rules/index.mjs +7 -1
  15. package/eslint/rules/no-direct-signal-mutation.mjs +240 -136
  16. package/eslint/rules/no-uninvoked-signal-in-template.mjs +170 -0
  17. package/eslint/signal-names.mjs +232 -0
  18. package/eslint/template-ast.mjs +26 -0
  19. package/fesm2022/dsivd-prestations-ng.mjs +7 -9
  20. package/fesm2022/dsivd-prestations-ng.mjs.map +1 -1
  21. package/package.json +1 -1
  22. package/src/eslint/configs/__tests__/configs.test.mjs +135 -0
  23. package/src/eslint/configs/template-base.mjs +10 -0
  24. package/src/eslint/configs/template-recommended.mjs +24 -0
  25. package/src/eslint/configs/template-rules.mjs +17 -0
  26. package/src/eslint/configs/ts-base.mjs +10 -0
  27. package/src/eslint/configs/ts-recommended.mjs +142 -0
  28. package/src/eslint/configs/ts-rules.mjs +14 -0
  29. package/src/eslint/index.mjs +20 -5
  30. package/src/eslint/rules/__tests__/no-direct-signal-mutation.test.mjs +86 -4
  31. package/src/eslint/rules/__tests__/no-uninvoked-signal-in-template.test.mjs +291 -0
  32. package/src/eslint/rules/index.mjs +7 -1
  33. package/src/eslint/rules/no-direct-signal-mutation.mjs +240 -136
  34. package/src/eslint/rules/no-uninvoked-signal-in-template.mjs +170 -0
  35. package/src/eslint/signal-names.mjs +232 -0
  36. package/src/eslint/template-ast.mjs +26 -0
  37. package/types/dsivd-prestations-ng.d.ts +0 -2
  38. package/dsivd-prestations-ng-19.0.6-beta.2.tgz +0 -0
  39. package/eslint/rules/__tests__/no-direct-signal-mutation.test.mjs +0 -98
package/CHANGELOG.md CHANGED
@@ -6,6 +6,37 @@
6
6
 
7
7
  ---
8
8
 
9
+ ## [19.0.7]
10
+
11
+ ### Added
12
+
13
+ - ESLint plugin: **shareable configs**, documented in [ESLINT_PLUGIN.md](ESLINT_PLUGIN.md)
14
+ - add `configs.tsRecommended` and `configs.templateRecommended` to the `extends` of your
15
+ `**/*.ts` and `**/*.html` blocks — see [UPGRADING_V19.md](UPGRADING_V19.md). They carry
16
+ the whole shared setup: the `eslint`, `typescript-eslint` and `angular-eslint`
17
+ baselines, the house conventions, and the rules below. Nothing else is needed: no
18
+ `plugins` declaration, no rule list.
19
+ - `configs.tsRules` and `configs.templateRules` enable only the rules written here, for a
20
+ project that supplies its own style
21
+ - 🚨 do **not** also spread `angular.configs.tsRecommended` or
22
+ `angular.configs.templateRecommended`: they are already included, and declaring the
23
+ same plugin twice makes ESLint fail outright
24
+ - 🚨 **Prerequisite**: `lintFilePatterns` in your `angular.json` must include
25
+ `src/**/*.html`, otherwise the template rules never run
26
+ - 🚨 **A rule added to these configs makes your lint fail on `npm update`.** This is
27
+ intentional. Every addition will be listed here, and a rule can be turned off from your
28
+ own `rules` block.
29
+ - ESLint rule [`no-direct-signal-mutation`](ESLINT_PLUGIN.md#no-direct-signal-mutation)
30
+ - reports an assignment into a member of a signal's value
31
+ (`model().property = value`), which mutates the object without notifying the signal.
32
+ Covers TypeScript files and templates, including the mutation hidden in a two-way
33
+ binding (`[(ngModel)]="model().property"`)
34
+ - ESLint rule [`no-uninvoked-signal-in-template`](ESLINT_PLUGIN.md#no-uninvoked-signal-in-template)
35
+ - reports a signal referenced in a template without being called (`{{ mySignal }}`,
36
+ `!mySignal`, `mySignal + 'x'`), which the Angular compiler only partially catches.
37
+ Autofixable. No configuration: inherited signals are resolved by walking the `extends`
38
+ chain of the component
39
+
9
40
  ## [19.0.2]
10
41
 
11
42
  ### Fixed
package/CONTRIBUTING.md CHANGED
@@ -122,6 +122,31 @@ You can auto-format the code on save with IntelliJ. More informations
122
122
 
123
123
  Please make sure you have followed the **Before committing** section so you will see formating/linting errors before you commit.
124
124
 
125
+ ### Adding a rule to the ESLint plugin
126
+
127
+ The library ships its own ESLint plugin (see [ESLINT_PLUGIN.md](ESLINT_PLUGIN.md)). Its rules
128
+ reach every consuming project through shareable configs, so **adding a rule breaks the lint of
129
+ those projects on their next `npm update`**. That is the intent, but it makes the checklist
130
+ below mandatory:
131
+
132
+ 1. Write the rule in `projects/prestations-ng/src/eslint/rules/<rule-name>.mjs`
133
+ 2. Register it in `rules/index.mjs`, keyed by its public kebab-case name
134
+ 3. Enable it in `configs/ts-rules.mjs`, `configs/template-rules.mjs`, or both depending on
135
+ what it inspects — the `*-recommended.mjs` presets pick those layers up. A change to a
136
+ shared convention rather than to a rule of ours goes into `configs/ts-recommended.mjs`.
137
+ 4. Test it in `rules/__tests__/<rule-name>.test.mjs` — use the `@angular-eslint/template-parser`
138
+ for a template rule
139
+ 5. Run `npm run test:eslint`, then `npm run lint:js` on the whole repository: a rule that
140
+ reports on the library's own code is a rule that will report on every project
141
+ 6. Document it in `ESLINT_PLUGIN.md`: what it catches, ✗/✓ examples, whether it autofixes, and
142
+ its known limitations
143
+ 7. Announce it in `CHANGELOG.md` **as a breaking change for consumers**, with the line needed
144
+ to turn it off
145
+
146
+ Step 5 matters more than it looks: the config tests in `configs/__tests__/configs.test.mjs`
147
+ guard against a config that references a rule which does not exist — a mistake that would
148
+ break every consuming project at once.
149
+
125
150
  ## Publishing a new version of the package
126
151
 
127
152
  Please use the dedicated Jenkins build:
@@ -0,0 +1,198 @@
1
+ # ESLint plugin
2
+
3
+ `@dsivd/prestations-ng` ships an ESLint plugin with rules that catch signal misuses the
4
+ Angular compiler lets through, plus the lint setup shared by every project built on the
5
+ library. It is published as a subpath of the library, so both follow the version of the
6
+ library.
7
+
8
+ ## Setup
9
+
10
+ The plugin exposes **shareable configs**, in the same spirit as `angular-eslint`. Add them to
11
+ the `extends` of the `**/*.ts` and `**/*.html` blocks of your `eslint.config.mjs`:
12
+
13
+ ```ts
14
+ import angular from 'angular-eslint';
15
+ import tseslint from 'typescript-eslint';
16
+ import prestationsNg from '@dsivd/prestations-ng/eslint';
17
+
18
+ export default tseslint.config(
19
+ {
20
+ files: ['**/*.ts'],
21
+ extends: [...prestationsNg.configs.tsRecommended],
22
+ languageOptions: {
23
+ parserOptions: {
24
+ projectService: true,
25
+ tsconfigRootDir: import.meta.dirname,
26
+ },
27
+ },
28
+ processor: angular.processInlineTemplates,
29
+ },
30
+ {
31
+ files: ['**/*.html'],
32
+ extends: [...prestationsNg.configs.templateRecommended],
33
+ },
34
+ );
35
+ ```
36
+
37
+ That is all. You do **not** declare `plugins`, and you do **not** list the rules — both come
38
+ from the config. Inline templates written in a `@Component` are covered too, through
39
+ `angular.processInlineTemplates`.
40
+
41
+ > 🚨 **Do not spread `angular.configs.tsRecommended` or `angular.configs.templateRecommended`
42
+ > alongside these configs.** They already include them. Declaring the same plugin twice from
43
+ > two different copies makes ESLint fail outright, before linting anything.
44
+
45
+ > 🚨 **Prerequisite**: `lintFilePatterns` in your `angular.json` must include
46
+ > `src/**/*.html`. Without it `ng lint` never opens your templates, and the template rules
47
+ > silently never run.
48
+
49
+ ## The configs
50
+
51
+ | Config | Target | Contents |
52
+ | ----------------------------- | ----------- | -------------------------------------------------------------- |
53
+ | `configs.tsRecommended` | `**/*.ts` | the baselines, the house style, and `tsRules` |
54
+ | `configs.templateRecommended` | `**/*.html` | the baselines, the house style, and `templateRules` |
55
+ | `configs.tsRules` | `**/*.ts` | `no-direct-signal-mutation` |
56
+ | `configs.templateRules` | `**/*.html` | `no-direct-signal-mutation`, `no-uninvoked-signal-in-template` |
57
+
58
+ The `*Recommended` presets are what a project normally extends. On top of the rules written
59
+ here they bring:
60
+
61
+ - the baselines — `eslint.configs.recommended`, `typescript-eslint` recommended and
62
+ stylistic, `angular-eslint` recommended, and for templates its accessibility set;
63
+ - the house style — naming and selector conventions, import ordering
64
+ (`simple-import-sort`, `import-x`), rxjs conventions, and the signal rules of
65
+ `angular-eslint` (`no-uncalled-signals`, `prefer-signals`, `prefer-signal-model`).
66
+
67
+ Use `tsRules` / `templateRules` instead when a project supplies its own style and only wants
68
+ the rules of this library. They register the plugin and nothing else, so you keep control of
69
+ the baselines — and stay responsible for keeping them aligned.
70
+
71
+ `no-direct-signal-mutation` appears in both targets: the rule carries one visitor for
72
+ TypeScript and one for the Angular template AST, and each fires on its own kind of file.
73
+
74
+ ## What happens when you update the library
75
+
76
+ A new rule added to the library lands in the config, so `npm update @dsivd/prestations-ng`
77
+ **will make your lint fail** on the code that violates it. This is intentional: it is how a
78
+ new practice gets rolled out across every project using the library. Every added rule is
79
+ listed in the [CHANGELOG](CHANGELOG.md).
80
+
81
+ If you cannot fix the violations right away, turn the rule off — or lower its severity — in
82
+ your own `rules` block. It is applied after the shared config, so it wins:
83
+
84
+ ```ts
85
+ {
86
+ files: ['**/*.html'],
87
+ extends: [...prestationsNg.configs.templateRecommended],
88
+ rules: {
89
+ // decided by this project, survives library updates
90
+ '@dsivd/prestations-ng/no-direct-signal-mutation': ['off'],
91
+ },
92
+ },
93
+ ```
94
+
95
+ ## Rules
96
+
97
+ ### `no-direct-signal-mutation`
98
+
99
+ Reports an assignment into a member of a signal's value. Mutating the object held by a
100
+ signal does not notify its dependents, so the view is not refreshed.
101
+
102
+ Applies to **TypeScript files and templates**, including the case where the mutation hides
103
+ in a two-way binding.
104
+
105
+ ```ts
106
+ // ✗ mutates the object inside the signal, no notification
107
+ this.model().property = value;
108
+
109
+ // ✓
110
+ this.model.update((current) => ({ ...current, property: value }));
111
+ ```
112
+
113
+ ```html
114
+ <!-- ✗ -->
115
+ <button (click)="model().property = value">…</button>
116
+ <input [(ngModel)]="model().property" />
117
+
118
+ <!-- ✓ -->
119
+ <button (click)="model.update((v) => ({ ...v, property: value }))">…</button>
120
+ ```
121
+
122
+ All the assignment operators accepted by the Angular expression parser are covered: `=`,
123
+ `+=`, `-=`, `*=`, `/=`, `%=`, `**=`, `&&=`, `||=`, `??=`.
124
+
125
+ **Not reported**, because the call is not assumed to be a signal read:
126
+
127
+ | Expression | Why |
128
+ | --------------------------------------------------- | ----------------------------------------------------------------- |
129
+ | `map.get(key).files = []` | the call takes arguments |
130
+ | `component.inputElement().nativeElement.value = ''` | reached through a foreign receiver, not `this` |
131
+ | `obj.getter().property = value` | idem |
132
+ | `viewChild(…)` / `viewChild.required(…)` accessors | a view query signal is read-only, there is no `.set()` to suggest |
133
+
134
+ The rule has no autofix: the correct rewrite depends on what you meant.
135
+
136
+ **Known limitation.** The view query exclusion needs the component class, which a template
137
+ AST does not carry. A mutation reached through a view query **from a template** is therefore
138
+ still reported, with a message suggesting a `.update()` that does not exist on a read-only
139
+ signal. It is rare — mutating the DOM from a template expression — but if you hit it, turn
140
+ the rule off for that file. The same imprecision applies to `computed()` in TypeScript,
141
+ which is read-only as well.
142
+
143
+ ### `no-uninvoked-signal-in-template`
144
+
145
+ Reports a signal referenced in a template without being called. A bare reference evaluates
146
+ to the function itself: always truthy, and serialized as source code when interpolated.
147
+
148
+ Applies to **templates only** (external and inline).
149
+
150
+ ```html
151
+ <!-- ✗ -->
152
+ <p>{{ userName }}</p>
153
+ <p>{{ userName + '!' }}</p>
154
+ <div [hidden]="!isLoading">…</div>
155
+
156
+ <!-- ✓ -->
157
+ <p>{{ userName() }}</p>
158
+ <p>{{ userName() + '!' }}</p>
159
+ <div [hidden]="!isLoading()">…</div>
160
+ ```
161
+
162
+ A control flow condition counts too: `@if (isLoading)` must be `@if (isLoading())`.
163
+
164
+ The Angular compiler only covers part of these cases (`NG8109` / `NG8117`): it lets the
165
+ negation and the concatenation through.
166
+
167
+ The rule is **autofixable** — `npm run lint` with `--fix` adds the missing parentheses.
168
+
169
+ Signal names are resolved automatically, with no configuration: the rule reads the twin
170
+ `.ts` of the template and walks its whole inheritance chain, resolving each `extends` the
171
+ way TypeScript would — relative paths, `node_modules`, `paths` mappings. Signals inherited
172
+ from `FoehnInputComponent`, `AbstractPageComponent` or from one of your own base classes are
173
+ found without declaring anything.
174
+
175
+ **Not reported:**
176
+
177
+ | Expression | Why |
178
+ | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------- |
179
+ | `mySignal()`, `mySignal?.()` | already called |
180
+ | `mySignal.set(…)`, `.update(…)`, `.asReadonly()` | targets the signal, not its value |
181
+ | `[(value)]="mySignal"` | a writable signal is two-way bound **without** parentheses, Angular calls `.set()` itself |
182
+ | `#ref`, `@for` items, `@if … as`, `@let`, `let-x`, `$index`… | template-local names shadow the class members |
183
+
184
+ ## The TypeScript counterpart
185
+
186
+ `@angular-eslint/no-uncalled-signals` covers the same class of bug **in TypeScript**, using
187
+ the real type checker — far more reliable than what a template rule can do. It cannot run on
188
+ templates: it requires type information, which a `.html` file has no program for. The two
189
+ are complementary, not redundant.
190
+
191
+ `configs.tsRecommended` already enables it. With `configs.tsRules` you enable it yourself:
192
+
193
+ ```ts
194
+ '@angular-eslint/no-uncalled-signals': 'error',
195
+ ```
196
+
197
+ It only reports signals used in logic positions (`if`, `!x`, `&&`, `===`, `.length`…), not a
198
+ bare `const x = mySignal`.
package/README.md CHANGED
@@ -20,6 +20,9 @@ The documentation is available online at [https://dsi-vd.github.io/prestations-n
20
20
  - If you're using **Angular 6 or 7**, use `@dsivd/prestations-ng` at version `^8`.
21
21
  - If you're using **Angular 5**, use `prestations-ng` at version `^7`.
22
22
 
23
+ The library also ships an ESLint plugin whose rules apply to your project: see
24
+ [the plugin guide](ESLINT_PLUGIN.md).
25
+
23
26
  Migration guides are available to provide an upgrade path from one major version to another.
24
27
 
25
28
  - If you need to upgrade to version `19+`, please refer to [the V19 upgrade guide](UPGRADING_V19.md).
package/UPGRADING_V19.md CHANGED
@@ -381,7 +381,7 @@ ng update ngx-matomo-client@9
381
381
 
382
382
  - In your `.eslintrc.json`, remove `"/src/main.ts",` from `ignorePatterns`.
383
383
  - In `main.ts`, replace `platformBrowserDynamic` by `platformBrowser`.
384
- - When using `<foehn-list-summary` component, replace `list` by `model`.
384
+ - When using `<foehn-list-summary` component, replace `[list]` by `[(model)]` (with double binding to handle item being removed).
385
385
 
386
386
  ### Now fix your project before continuing
387
387
 
@@ -1085,14 +1085,11 @@ rm .eslintrc.json
1085
1085
 
1086
1086
  ```ts
1087
1087
  // @ts-check
1088
- import eslint from '@eslint/js';
1088
+ import prettier from 'eslint-config-prettier';
1089
1089
  import tseslint from 'typescript-eslint';
1090
1090
  import angular from 'angular-eslint';
1091
- import rxjs from '@smarttools/eslint-plugin-rxjs';
1092
- import simpleImportSort from 'eslint-plugin-simple-import-sort';
1093
- import prettier from 'eslint-config-prettier';
1094
- import importX from 'eslint-plugin-import-x';
1095
- import prestationsNgEslint from '@dsivd/prestations-ng/eslint';
1091
+
1092
+ import prestationsNg from '@dsivd/prestations-ng/eslint';
1096
1093
 
1097
1094
  export default tseslint.config(
1098
1095
  {
@@ -1102,154 +1099,20 @@ export default tseslint.config(
1102
1099
  // ─── TypeScript ────────────────────────────────────────────────────────────
1103
1100
  {
1104
1101
  files: ['**/*.ts'],
1105
- extends: [
1106
- eslint.configs.recommended,
1107
- ...tseslint.configs.recommended, // covers: no-explicit-any, ban-ts-comment,
1108
- // no-unused-expressions, no-var, no-unused-vars…
1109
- ...tseslint.configs.stylistic, // covers: array-type, consistent-type-assertions,
1110
- // dot-notation, no-inferrable-types (default options),
1111
- // no-empty-function (error), prefer-for-of,
1112
- // prefer-function-type…
1113
- ...angular.configs.tsRecommended, // covers: prefer-inject, no-empty-lifecycle-method,
1114
- // use-lifecycle-interface…
1115
- ],
1102
+ extends: [...prestationsNg.configs.tsRecommended],
1116
1103
  languageOptions: {
1117
1104
  parserOptions: {
1118
1105
  projectService: true,
1119
1106
  tsconfigRootDir: import.meta.dirname,
1120
1107
  },
1121
1108
  },
1122
- plugins: {
1123
- rxjs,
1124
- 'simple-import-sort': simpleImportSort,
1125
- 'import-x': importX,
1126
- '@dsivd/prestations-ng': prestationsNgEslint,
1127
- },
1128
1109
  processor: angular.processInlineTemplates,
1129
- rules: {
1130
- // ── @angular-eslint ──────────────────────────────────────────────
1131
- '@angular-eslint/component-selector': [
1132
- 'error',
1133
- {
1134
- type: 'element',
1135
- prefix: 'app',
1136
- style: 'kebab-case',
1137
- },
1138
- ],
1139
- '@angular-eslint/directive-selector': [
1140
- 'error',
1141
- {
1142
- type: 'attribute',
1143
- prefix: 'app',
1144
- style: 'camelCase',
1145
- },
1146
- ],
1147
- '@angular-eslint/no-uncalled-signals': ['error'],
1148
- '@angular-eslint/prefer-signal-model': ['error'],
1149
- '@angular-eslint/prefer-signals': ['error'],
1150
-
1151
- // ── @typescript-eslint ───────────────────────────────────────────
1152
- '@typescript-eslint/explicit-function-return-type': [
1153
- 'error',
1154
- {
1155
- allowExpressions: true,
1156
- allowTypedFunctionExpressions: true,
1157
- allowHigherOrderFunctions: true,
1158
- allowDirectConstAssertionInArrowFunctions: true,
1159
- },
1160
- ],
1161
- '@typescript-eslint/explicit-member-accessibility': [
1162
- 'error',
1163
- { accessibility: 'no-public' },
1164
- ],
1165
- '@typescript-eslint/member-delimiter-style': 'error',
1166
- '@typescript-eslint/member-ordering': 'error',
1167
- '@typescript-eslint/no-base-to-string': 'warn',
1168
- // stylistic enables no-inferrable-types with default options (ignoreParameters: false);
1169
- // override here to allow typed parameters explicitly
1170
- '@typescript-eslint/no-inferrable-types': [
1171
- 'error',
1172
- { ignoreParameters: true },
1173
- ],
1174
- // stylistic sets no-empty-function to error; downgrade to warn
1175
- '@typescript-eslint/no-empty-function': 'warn',
1176
- '@typescript-eslint/prefer-includes': 'warn',
1177
- '@typescript-eslint/return-await': ['error', 'never'],
1178
- '@typescript-eslint/typedef': ['error', { parameter: true }],
1179
- '@typescript-eslint/unified-signatures': 'error',
1180
- // off here — @typescript-eslint/no-shadow handles it correctly for TS
1181
- 'no-shadow': 'off',
1182
- '@typescript-eslint/no-shadow': ['error', { hoist: 'all' }],
1183
- '@typescript-eslint/no-unused-vars': [
1184
- 'error',
1185
- {
1186
- varsIgnorePattern: '^_',
1187
- argsIgnorePattern: '^_',
1188
- caughtErrorsIgnorePattern: '^_',
1189
- },
1190
- ],
1191
-
1192
- // ── eslint core ──────────────────────────────────────────────────
1193
- 'arrow-body-style': 'error',
1194
- 'arrow-parens': ['error', 'as-needed'],
1195
- curly: 'error',
1196
- eqeqeq: ['error', 'always'],
1197
- 'guard-for-in': 'error',
1198
- 'no-bitwise': 'error',
1199
- 'no-caller': 'error',
1200
- 'no-console': ['error', { allow: ['log', 'warn', 'error'] }],
1201
- 'no-eval': 'error',
1202
- 'no-extra-boolean-cast': 'off',
1203
- 'no-multiple-empty-lines': 'error',
1204
- 'no-new-wrappers': 'error',
1205
- 'no-restricted-imports': [
1206
- 'error',
1207
- { paths: ['rxjs/Rx', 'primeng/primeng', 'primeng'] },
1208
- ],
1209
- 'no-return-assign': 'error',
1210
- 'no-throw-literal': 'error',
1211
- 'no-undef-init': 'error',
1212
- 'no-useless-concat': 'error',
1213
- 'object-shorthand': ['error', 'always', { avoidQuotes: true }],
1214
- 'one-var': ['error', 'never'],
1215
- 'prefer-arrow-callback': 'error',
1216
- 'prefer-template': 'error',
1217
- radix: 'error',
1218
-
1219
- // ── rxjs ─────────────────────────────────────────────────────────
1220
- 'rxjs/no-implicit-any-catch': ['error', { allowExplicitAny: true }],
1221
- 'rxjs/no-sharereplay': 'off',
1222
-
1223
- // ── import-x ─────────────────────────────────────────────────────
1224
- 'import-x/no-cycle': [
1225
- 'warn',
1226
- { maxDepth: 3, ignoreExternal: true },
1227
- ],
1228
- 'import-x/no-deprecated': 'warn',
1229
- 'import-x/first': 'error',
1230
- 'import-x/newline-after-import': 'error',
1231
- 'import-x/no-duplicates': 'error',
1232
-
1233
- // ── simple-import-sort ───────────────────────────────────────────
1234
- 'simple-import-sort/imports': 'error',
1235
- 'simple-import-sort/exports': 'error',
1236
-
1237
- // ── prestations-ng ──────────────────────────────────────────────
1238
- '@dsivd/prestations-ng/no-direct-signal-mutation': 'error',
1239
- },
1240
1110
  },
1241
1111
 
1242
1112
  // ─── HTML templates ────────────────────────────────────────────────────────
1243
1113
  {
1244
1114
  files: ['**/*.html'],
1245
- extends: [
1246
- ...angular.configs.templateRecommended,
1247
- ...angular.configs.templateAccessibility,
1248
- ],
1249
- rules: {
1250
- '@angular-eslint/template/no-negated-async': 'off',
1251
- '@angular-eslint/template/button-has-type': 'error',
1252
- },
1115
+ extends: [...prestationsNg.configs.templateRecommended],
1253
1116
  },
1254
1117
 
1255
1118
  // ─── Prettier (must be last — disables formatting rules) ───────────────────
@@ -1257,6 +1120,38 @@ export default tseslint.config(
1257
1120
  );
1258
1121
  ```
1259
1122
 
1123
+ That is the whole file. The two `*Recommended` configs come from the library and carry
1124
+ everything: the `eslint`, `typescript-eslint` and `angular-eslint` baselines, the template
1125
+ accessibility set, the naming and import conventions, and the rules written by the library
1126
+ itself. You declare no `plugins` and list no rule.
1127
+
1128
+ The packages installed above are still required — the library's config imports them from your
1129
+ `node_modules` rather than shipping its own copies. For the same reason, do **not** spread
1130
+ `angular.configs.tsRecommended` or `angular.configs.templateRecommended` yourself: they are
1131
+ already included, and declaring the same plugin twice from two different copies makes ESLint
1132
+ fail outright.
1133
+
1134
+ #### prestations-ng ESLint rules
1135
+
1136
+ The library ships **shareable custom configs** that are auto-updatable with the library version. Two
1137
+ rules are enabled today — `no-direct-signal-mutation` and `no-uninvoked-signal-in-template` — and both
1138
+ are documented in [the plugin guide](ESLINT_PLUGIN.md), alongside the leaner `tsRules` / `templateRules`
1139
+ configs for a project that supplies its own style.
1140
+
1141
+ One consequence to keep in mind:
1142
+
1143
+ - A rule added — or tightened — in a later version of the library lands in the config, so
1144
+ `npm update` **will make your lint fail** on the offending code. That is the point — it is
1145
+ how a practice is rolled out to every project. Each addition is listed in the
1146
+ [CHANGELOG](CHANGELOG.md), and a rule you cannot fix right away can be turned off in your
1147
+ own `rules` block, which is applied after the shared config:
1148
+
1149
+ ```ts
1150
+ rules: {
1151
+ '@dsivd/prestations-ng/no-direct-signal-mutation': ['off'],
1152
+ },
1153
+ ```
1154
+
1260
1155
  #### Delete `typings.d.ts`
1261
1156
 
1262
1157
  In `front/src/`, remove the file `typings.d.ts` if it is still there.
Binary file
@@ -0,0 +1,10 @@
1
+ // Registers the plugin without enabling any rule. Meant to be used through
2
+ // `templateRules` or `templateRecommended`, inside the `extends` of a block that
3
+ // already targets `**/*.html` and sets the Angular template parser.
4
+ const templateBase = (plugin) => ({
5
+ name: 'prestations-ng/template-base',
6
+ plugins: {
7
+ '@dsivd/prestations-ng': plugin,
8
+ },
9
+ });
10
+ export default templateBase;
@@ -0,0 +1,24 @@
1
+ import angular from 'angular-eslint';
2
+
3
+ import templateRules from './template-rules.mjs';
4
+
5
+ /**
6
+ * The house Angular template preset: the recommended and accessibility baselines, this
7
+ * library's own rules, and the conventions every prestations-ng project follows.
8
+ *
9
+ * Meant for the `extends` of a `**\/*.html` block. It brings its own parser and plugins,
10
+ * so do not spread `angular.configs.templateRecommended` alongside it.
11
+ */
12
+ const templateRecommended = (plugin) => [
13
+ ...angular.configs.templateRecommended,
14
+ ...angular.configs.templateAccessibility,
15
+ ...templateRules(plugin),
16
+ {
17
+ name: 'prestations-ng/template-recommended',
18
+ rules: {
19
+ '@angular-eslint/template/no-negated-async': 'off',
20
+ '@angular-eslint/template/button-has-type': 'error',
21
+ },
22
+ },
23
+ ];
24
+ export default templateRecommended;
@@ -0,0 +1,17 @@
1
+ import templateBase from './template-base.mjs';
2
+
3
+ // Only the rules written by this library. `templateRecommended` includes it, alongside
4
+ // the house style; use this layer directly to opt out of the latter.
5
+ const templateRules = (plugin) => [
6
+ templateBase(plugin),
7
+ {
8
+ name: 'prestations-ng/template-rules',
9
+ rules: {
10
+ // Also in `tsRules`: the rule carries both an ESTree and an Angular
11
+ // template visitor, and each fires on its own kind of file.
12
+ '@dsivd/prestations-ng/no-direct-signal-mutation': 'error',
13
+ '@dsivd/prestations-ng/no-uninvoked-signal-in-template': 'error',
14
+ },
15
+ },
16
+ ];
17
+ export default templateRules;
@@ -0,0 +1,10 @@
1
+ // Registers the plugin without enabling any rule. Meant to be used through
2
+ // `tsRules` or `tsRecommended`, inside the `extends` of a block that already
3
+ // targets `**/*.ts` and sets the TypeScript parser.
4
+ const tsBase = (plugin) => ({
5
+ name: 'prestations-ng/ts-base',
6
+ plugins: {
7
+ '@dsivd/prestations-ng': plugin,
8
+ },
9
+ });
10
+ export default tsBase;