@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.
- package/CHANGELOG.md +31 -0
- package/CONTRIBUTING.md +25 -0
- package/ESLINT_PLUGIN.md +198 -0
- package/README.md +3 -0
- package/UPGRADING_V19.md +38 -143
- package/dsivd-prestations-ng-19.0.7.tgz +0 -0
- package/eslint/configs/template-base.mjs +10 -0
- package/eslint/configs/template-recommended.mjs +24 -0
- package/eslint/configs/template-rules.mjs +17 -0
- package/eslint/configs/ts-base.mjs +10 -0
- package/eslint/configs/ts-recommended.mjs +142 -0
- package/eslint/configs/ts-rules.mjs +14 -0
- package/eslint/index.mjs +20 -5
- package/eslint/rules/index.mjs +7 -1
- package/eslint/rules/no-direct-signal-mutation.mjs +240 -136
- package/eslint/rules/no-uninvoked-signal-in-template.mjs +170 -0
- package/eslint/signal-names.mjs +232 -0
- package/eslint/template-ast.mjs +26 -0
- package/fesm2022/dsivd-prestations-ng.mjs +7 -9
- package/fesm2022/dsivd-prestations-ng.mjs.map +1 -1
- package/package.json +1 -1
- package/src/eslint/configs/__tests__/configs.test.mjs +135 -0
- package/src/eslint/configs/template-base.mjs +10 -0
- package/src/eslint/configs/template-recommended.mjs +24 -0
- package/src/eslint/configs/template-rules.mjs +17 -0
- package/src/eslint/configs/ts-base.mjs +10 -0
- package/src/eslint/configs/ts-recommended.mjs +142 -0
- package/src/eslint/configs/ts-rules.mjs +14 -0
- package/src/eslint/index.mjs +20 -5
- package/src/eslint/rules/__tests__/no-direct-signal-mutation.test.mjs +86 -4
- package/src/eslint/rules/__tests__/no-uninvoked-signal-in-template.test.mjs +291 -0
- package/src/eslint/rules/index.mjs +7 -1
- package/src/eslint/rules/no-direct-signal-mutation.mjs +240 -136
- package/src/eslint/rules/no-uninvoked-signal-in-template.mjs +170 -0
- package/src/eslint/signal-names.mjs +232 -0
- package/src/eslint/template-ast.mjs +26 -0
- package/types/dsivd-prestations-ng.d.ts +0 -2
- package/dsivd-prestations-ng-19.0.6-beta.2.tgz +0 -0
- 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:
|
package/ESLINT_PLUGIN.md
ADDED
|
@@ -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
|
|
1088
|
+
import prettier from 'eslint-config-prettier';
|
|
1089
1089
|
import tseslint from 'typescript-eslint';
|
|
1090
1090
|
import angular from 'angular-eslint';
|
|
1091
|
-
|
|
1092
|
-
import
|
|
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;
|