@dsivd/prestations-ng 19.0.6 → 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 +10 -3
- package/CONTRIBUTING.md +3 -2
- package/ESLINT_PLUGIN.md +41 -20
- package/UPGRADING_V19.md +22 -145
- package/dsivd-prestations-ng-19.0.7.tgz +0 -0
- package/eslint/configs/template-base.mjs +2 -2
- package/eslint/configs/template-recommended.mjs +15 -6
- package/eslint/configs/template-rules.mjs +17 -0
- package/eslint/configs/ts-base.mjs +2 -2
- package/eslint/configs/ts-recommended.mjs +133 -3
- package/eslint/configs/ts-rules.mjs +14 -0
- package/eslint/index.mjs +10 -4
- package/package.json +1 -1
- package/src/eslint/configs/__tests__/configs.test.mjs +113 -48
- package/src/eslint/configs/template-base.mjs +2 -2
- package/src/eslint/configs/template-recommended.mjs +15 -6
- package/src/eslint/configs/template-rules.mjs +17 -0
- package/src/eslint/configs/ts-base.mjs +2 -2
- package/src/eslint/configs/ts-recommended.mjs +133 -3
- package/src/eslint/configs/ts-rules.mjs +14 -0
- package/src/eslint/index.mjs +10 -4
- package/dsivd-prestations-ng-19.0.6.tgz +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -6,14 +6,21 @@
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
## [
|
|
9
|
+
## [19.0.7]
|
|
10
10
|
|
|
11
11
|
### Added
|
|
12
12
|
|
|
13
13
|
- ESLint plugin: **shareable configs**, documented in [ESLINT_PLUGIN.md](ESLINT_PLUGIN.md)
|
|
14
14
|
- add `configs.tsRecommended` and `configs.templateRecommended` to the `extends` of your
|
|
15
|
-
`**/*.ts` and `**/*.html` blocks — see [UPGRADING_V19.md](UPGRADING_V19.md).
|
|
16
|
-
|
|
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
|
|
17
24
|
- 🚨 **Prerequisite**: `lintFilePatterns` in your `angular.json` must include
|
|
18
25
|
`src/**/*.html`, otherwise the template rules never run
|
|
19
26
|
- 🚨 **A rule added to these configs makes your lint fail on `npm update`.** This is
|
package/CONTRIBUTING.md
CHANGED
|
@@ -131,8 +131,9 @@ below mandatory:
|
|
|
131
131
|
|
|
132
132
|
1. Write the rule in `projects/prestations-ng/src/eslint/rules/<rule-name>.mjs`
|
|
133
133
|
2. Register it in `rules/index.mjs`, keyed by its public kebab-case name
|
|
134
|
-
3. Enable it in `configs/ts-
|
|
135
|
-
|
|
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`.
|
|
136
137
|
4. Test it in `rules/__tests__/<rule-name>.test.mjs` — use the `@angular-eslint/template-parser`
|
|
137
138
|
for a template rule
|
|
138
139
|
5. Run `npm run test:eslint`, then `npm run lint:js` on the whole repository: a rule that
|
package/ESLINT_PLUGIN.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# ESLint plugin
|
|
2
2
|
|
|
3
3
|
`@dsivd/prestations-ng` ships an ESLint plugin with rules that catch signal misuses the
|
|
4
|
-
Angular compiler lets through
|
|
5
|
-
follow the version of 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.
|
|
6
7
|
|
|
7
8
|
## Setup
|
|
8
9
|
|
|
@@ -17,18 +18,18 @@ import prestationsNg from '@dsivd/prestations-ng/eslint';
|
|
|
17
18
|
export default tseslint.config(
|
|
18
19
|
{
|
|
19
20
|
files: ['**/*.ts'],
|
|
20
|
-
extends: [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
extends: [...prestationsNg.configs.tsRecommended],
|
|
22
|
+
languageOptions: {
|
|
23
|
+
parserOptions: {
|
|
24
|
+
projectService: true,
|
|
25
|
+
tsconfigRootDir: import.meta.dirname,
|
|
26
|
+
},
|
|
27
|
+
},
|
|
24
28
|
processor: angular.processInlineTemplates,
|
|
25
29
|
},
|
|
26
30
|
{
|
|
27
31
|
files: ['**/*.html'],
|
|
28
|
-
extends: [
|
|
29
|
-
...angular.configs.templateRecommended,
|
|
30
|
-
...prestationsNg.configs.templateRecommended,
|
|
31
|
-
],
|
|
32
|
+
extends: [...prestationsNg.configs.templateRecommended],
|
|
32
33
|
},
|
|
33
34
|
);
|
|
34
35
|
```
|
|
@@ -37,17 +38,38 @@ That is all. You do **not** declare `plugins`, and you do **not** list the rules
|
|
|
37
38
|
from the config. Inline templates written in a `@Component` are covered too, through
|
|
38
39
|
`angular.processInlineTemplates`.
|
|
39
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
|
+
|
|
40
45
|
> 🚨 **Prerequisite**: `lintFilePatterns` in your `angular.json` must include
|
|
41
46
|
> `src/**/*.html`. Without it `ng lint` never opens your templates, and the template rules
|
|
42
47
|
> silently never run.
|
|
43
48
|
|
|
44
|
-
|
|
49
|
+
## The configs
|
|
50
|
+
|
|
51
|
+
| Config | Target | Contents |
|
|
45
52
|
| ----------------------------- | ----------- | -------------------------------------------------------------- |
|
|
46
|
-
| `configs.tsRecommended` | `**/*.ts` | `
|
|
47
|
-
| `configs.templateRecommended` | `**/*.html` |
|
|
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:
|
|
48
60
|
|
|
49
|
-
|
|
50
|
-
|
|
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.
|
|
51
73
|
|
|
52
74
|
## What happens when you update the library
|
|
53
75
|
|
|
@@ -62,10 +84,7 @@ your own `rules` block. It is applied after the shared config, so it wins:
|
|
|
62
84
|
```ts
|
|
63
85
|
{
|
|
64
86
|
files: ['**/*.html'],
|
|
65
|
-
extends: [
|
|
66
|
-
...angular.configs.templateRecommended,
|
|
67
|
-
...prestationsNg.configs.templateRecommended,
|
|
68
|
-
],
|
|
87
|
+
extends: [...prestationsNg.configs.templateRecommended],
|
|
69
88
|
rules: {
|
|
70
89
|
// decided by this project, survives library updates
|
|
71
90
|
'@dsivd/prestations-ng/no-direct-signal-mutation': ['off'],
|
|
@@ -162,13 +181,15 @@ found without declaring anything.
|
|
|
162
181
|
| `[(value)]="mySignal"` | a writable signal is two-way bound **without** parentheses, Angular calls `.set()` itself |
|
|
163
182
|
| `#ref`, `@for` items, `@if … as`, `@let`, `let-x`, `$index`… | template-local names shadow the class members |
|
|
164
183
|
|
|
165
|
-
##
|
|
184
|
+
## The TypeScript counterpart
|
|
166
185
|
|
|
167
186
|
`@angular-eslint/no-uncalled-signals` covers the same class of bug **in TypeScript**, using
|
|
168
187
|
the real type checker — far more reliable than what a template rule can do. It cannot run on
|
|
169
188
|
templates: it requires type information, which a `.html` file has no program for. The two
|
|
170
189
|
are complementary, not redundant.
|
|
171
190
|
|
|
191
|
+
`configs.tsRecommended` already enables it. With `configs.tsRules` you enable it yourself:
|
|
192
|
+
|
|
172
193
|
```ts
|
|
173
194
|
'@angular-eslint/no-uncalled-signals': 'error',
|
|
174
195
|
```
|
package/UPGRADING_V19.md
CHANGED
|
@@ -1085,13 +1085,10 @@ 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 simpleImportSort from 'eslint-plugin-simple-import-sort';
|
|
1093
|
-
import prettier from 'eslint-config-prettier';
|
|
1094
|
-
import importX from 'eslint-plugin-import-x';
|
|
1091
|
+
|
|
1095
1092
|
import prestationsNg from '@dsivd/prestations-ng/eslint';
|
|
1096
1093
|
|
|
1097
1094
|
export default tseslint.config(
|
|
@@ -1102,152 +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
|
-
...prestationsNg.configs.tsRecommended, // covers: no-direct-signal-mutation
|
|
1116
|
-
],
|
|
1102
|
+
extends: [...prestationsNg.configs.tsRecommended],
|
|
1117
1103
|
languageOptions: {
|
|
1118
1104
|
parserOptions: {
|
|
1119
1105
|
projectService: true,
|
|
1120
1106
|
tsconfigRootDir: import.meta.dirname,
|
|
1121
1107
|
},
|
|
1122
1108
|
},
|
|
1123
|
-
plugins: {
|
|
1124
|
-
rxjs,
|
|
1125
|
-
'simple-import-sort': simpleImportSort,
|
|
1126
|
-
'import-x': importX,
|
|
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
1110
|
},
|
|
1238
1111
|
|
|
1239
1112
|
// ─── HTML templates ────────────────────────────────────────────────────────
|
|
1240
1113
|
{
|
|
1241
1114
|
files: ['**/*.html'],
|
|
1242
|
-
extends: [
|
|
1243
|
-
...angular.configs.templateRecommended,
|
|
1244
|
-
...angular.configs.templateAccessibility,
|
|
1245
|
-
...prestationsNg.configs.templateRecommended, // covers no-direct-signal-mutation, no-uninvoked-signal-in-template
|
|
1246
|
-
],
|
|
1247
|
-
rules: {
|
|
1248
|
-
'@angular-eslint/template/no-negated-async': 'off',
|
|
1249
|
-
'@angular-eslint/template/button-has-type': 'error',
|
|
1250
|
-
},
|
|
1115
|
+
extends: [...prestationsNg.configs.templateRecommended],
|
|
1251
1116
|
},
|
|
1252
1117
|
|
|
1253
1118
|
// ─── Prettier (must be last — disables formatting rules) ───────────────────
|
|
@@ -1255,19 +1120,31 @@ export default tseslint.config(
|
|
|
1255
1120
|
);
|
|
1256
1121
|
```
|
|
1257
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
|
+
|
|
1258
1134
|
#### prestations-ng ESLint rules
|
|
1259
1135
|
|
|
1260
1136
|
The library ships **shareable custom configs** that are auto-updatable with the library version. Two
|
|
1261
1137
|
rules are enabled today — `no-direct-signal-mutation` and `no-uninvoked-signal-in-template` — and both
|
|
1262
|
-
are documented in [the plugin guide](ESLINT_PLUGIN.md)
|
|
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.
|
|
1263
1140
|
|
|
1264
1141
|
One consequence to keep in mind:
|
|
1265
1142
|
|
|
1266
|
-
- A rule added in a later version of the library lands in the config, so
|
|
1267
|
-
make your lint fail** on the offending code. That is the point — it is
|
|
1268
|
-
rolled out to every project. Each addition is listed in the
|
|
1269
|
-
a rule you cannot fix right away can be turned off in your
|
|
1270
|
-
applied after the shared config:
|
|
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:
|
|
1271
1148
|
|
|
1272
1149
|
```ts
|
|
1273
1150
|
rules: {
|
|
Binary file
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Registers the plugin without enabling any rule. Meant to be used through
|
|
2
|
-
// `templateRecommended`, inside the `extends` of a block that
|
|
3
|
-
// `**/*.html` and sets the Angular template parser.
|
|
2
|
+
// `templateRules` or `templateRecommended`, inside the `extends` of a block that
|
|
3
|
+
// already targets `**/*.html` and sets the Angular template parser.
|
|
4
4
|
const templateBase = (plugin) => ({
|
|
5
5
|
name: 'prestations-ng/template-base',
|
|
6
6
|
plugins: {
|
|
@@ -1,14 +1,23 @@
|
|
|
1
|
-
import
|
|
1
|
+
import angular from 'angular-eslint';
|
|
2
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
|
+
*/
|
|
3
12
|
const templateRecommended = (plugin) => [
|
|
4
|
-
|
|
13
|
+
...angular.configs.templateRecommended,
|
|
14
|
+
...angular.configs.templateAccessibility,
|
|
15
|
+
...templateRules(plugin),
|
|
5
16
|
{
|
|
6
17
|
name: 'prestations-ng/template-recommended',
|
|
7
18
|
rules: {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
'@dsivd/prestations-ng/no-direct-signal-mutation': 'error',
|
|
11
|
-
'@dsivd/prestations-ng/no-uninvoked-signal-in-template': 'error',
|
|
19
|
+
'@angular-eslint/template/no-negated-async': 'off',
|
|
20
|
+
'@angular-eslint/template/button-has-type': 'error',
|
|
12
21
|
},
|
|
13
22
|
},
|
|
14
23
|
];
|
|
@@ -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;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Registers the plugin without enabling any rule. Meant to be used through
|
|
2
|
-
// `tsRecommended`, inside the `extends` of a block that already
|
|
3
|
-
// and sets the TypeScript parser.
|
|
2
|
+
// `tsRules` or `tsRecommended`, inside the `extends` of a block that already
|
|
3
|
+
// targets `**/*.ts` and sets the TypeScript parser.
|
|
4
4
|
const tsBase = (plugin) => ({
|
|
5
5
|
name: 'prestations-ng/ts-base',
|
|
6
6
|
plugins: {
|
|
@@ -1,11 +1,141 @@
|
|
|
1
|
-
import
|
|
1
|
+
import eslint from '@eslint/js';
|
|
2
|
+
import rxjs from '@smarttools/eslint-plugin-rxjs';
|
|
3
|
+
import angular from 'angular-eslint';
|
|
4
|
+
import importX from 'eslint-plugin-import-x';
|
|
5
|
+
import simpleImportSort from 'eslint-plugin-simple-import-sort';
|
|
6
|
+
import tseslint from 'typescript-eslint';
|
|
2
7
|
|
|
8
|
+
import tsRules from './ts-rules.mjs';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The house TypeScript preset: the shared baselines, this library's own rules, and the
|
|
12
|
+
* conventions every prestations-ng project follows.
|
|
13
|
+
*
|
|
14
|
+
* Meant for the `extends` of a `**\/*.ts` block. It brings its own parser and plugins,
|
|
15
|
+
* so do not spread `angular.configs.tsRecommended` alongside it — declaring the same
|
|
16
|
+
* plugin twice from two different copies makes ESLint fail outright.
|
|
17
|
+
*/
|
|
3
18
|
const tsRecommended = (plugin) => [
|
|
4
|
-
|
|
19
|
+
eslint.configs.recommended,
|
|
20
|
+
...tseslint.configs.recommended, // covers: no-explicit-any, ban-ts-comment,
|
|
21
|
+
// no-unused-expressions, no-var, no-unused-vars…
|
|
22
|
+
...tseslint.configs.stylistic, // covers: array-type, consistent-type-assertions,
|
|
23
|
+
// dot-notation, no-inferrable-types (default options),
|
|
24
|
+
// no-empty-function (error), prefer-for-of,
|
|
25
|
+
// prefer-function-type…
|
|
26
|
+
...angular.configs.tsRecommended, // covers: prefer-inject, no-empty-lifecycle-method,
|
|
27
|
+
// use-lifecycle-interface…
|
|
28
|
+
...tsRules(plugin),
|
|
5
29
|
{
|
|
6
30
|
name: 'prestations-ng/ts-recommended',
|
|
31
|
+
plugins: {
|
|
32
|
+
rxjs,
|
|
33
|
+
'simple-import-sort': simpleImportSort,
|
|
34
|
+
'import-x': importX,
|
|
35
|
+
},
|
|
7
36
|
rules: {
|
|
8
|
-
|
|
37
|
+
// ── @angular-eslint ──────────────────────────────────────────────
|
|
38
|
+
'@angular-eslint/component-selector': [
|
|
39
|
+
'error',
|
|
40
|
+
{
|
|
41
|
+
type: 'element',
|
|
42
|
+
prefix: 'app',
|
|
43
|
+
style: 'kebab-case',
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
'@angular-eslint/directive-selector': [
|
|
47
|
+
'error',
|
|
48
|
+
{
|
|
49
|
+
type: 'attribute',
|
|
50
|
+
prefix: 'app',
|
|
51
|
+
style: 'camelCase',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
'@angular-eslint/no-uncalled-signals': ['error'],
|
|
55
|
+
'@angular-eslint/prefer-signal-model': ['error'],
|
|
56
|
+
'@angular-eslint/prefer-signals': ['error'],
|
|
57
|
+
|
|
58
|
+
// ── @typescript-eslint ───────────────────────────────────────────
|
|
59
|
+
'@typescript-eslint/explicit-function-return-type': [
|
|
60
|
+
'error',
|
|
61
|
+
{
|
|
62
|
+
allowExpressions: true,
|
|
63
|
+
allowTypedFunctionExpressions: true,
|
|
64
|
+
allowHigherOrderFunctions: true,
|
|
65
|
+
allowDirectConstAssertionInArrowFunctions: true,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
'@typescript-eslint/explicit-member-accessibility': [
|
|
69
|
+
'error',
|
|
70
|
+
{ accessibility: 'no-public' },
|
|
71
|
+
],
|
|
72
|
+
'@typescript-eslint/member-ordering': 'error',
|
|
73
|
+
'@typescript-eslint/no-base-to-string': 'warn',
|
|
74
|
+
// stylistic enables no-inferrable-types with default options (ignoreParameters: false);
|
|
75
|
+
// override here to allow typed parameters explicitly
|
|
76
|
+
'@typescript-eslint/no-inferrable-types': [
|
|
77
|
+
'error',
|
|
78
|
+
{ ignoreParameters: true },
|
|
79
|
+
],
|
|
80
|
+
// stylistic sets no-empty-function to error; downgrade to warn
|
|
81
|
+
'@typescript-eslint/no-empty-function': 'warn',
|
|
82
|
+
'@typescript-eslint/prefer-includes': 'warn',
|
|
83
|
+
'@typescript-eslint/return-await': ['error', 'never'],
|
|
84
|
+
'@typescript-eslint/typedef': ['error', { parameter: true }],
|
|
85
|
+
'@typescript-eslint/unified-signatures': 'error',
|
|
86
|
+
// off here — @typescript-eslint/no-shadow handles it correctly for TS
|
|
87
|
+
'no-shadow': 'off',
|
|
88
|
+
'@typescript-eslint/no-shadow': ['error', { hoist: 'all' }],
|
|
89
|
+
'@typescript-eslint/no-unused-vars': [
|
|
90
|
+
'error',
|
|
91
|
+
{
|
|
92
|
+
varsIgnorePattern: '^_',
|
|
93
|
+
argsIgnorePattern: '^_',
|
|
94
|
+
caughtErrorsIgnorePattern: '^_',
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
|
|
98
|
+
// ── eslint core ──────────────────────────────────────────────────
|
|
99
|
+
'arrow-body-style': 'error',
|
|
100
|
+
'arrow-parens': ['error', 'as-needed'],
|
|
101
|
+
curly: 'error',
|
|
102
|
+
eqeqeq: ['error', 'always'],
|
|
103
|
+
'guard-for-in': 'error',
|
|
104
|
+
'no-bitwise': 'error',
|
|
105
|
+
'no-caller': 'error',
|
|
106
|
+
'no-console': ['error', { allow: ['log', 'warn', 'error'] }],
|
|
107
|
+
'no-eval': 'error',
|
|
108
|
+
'no-extra-boolean-cast': 'off',
|
|
109
|
+
'no-multiple-empty-lines': 'error',
|
|
110
|
+
'no-new-wrappers': 'error',
|
|
111
|
+
'no-restricted-imports': [
|
|
112
|
+
'error',
|
|
113
|
+
{ paths: ['rxjs/Rx', 'primeng/primeng', 'primeng'] },
|
|
114
|
+
],
|
|
115
|
+
'no-return-assign': 'error',
|
|
116
|
+
'no-throw-literal': 'error',
|
|
117
|
+
'no-undef-init': 'error',
|
|
118
|
+
'no-useless-concat': 'error',
|
|
119
|
+
'object-shorthand': ['error', 'always', { avoidQuotes: true }],
|
|
120
|
+
'one-var': ['error', 'never'],
|
|
121
|
+
'prefer-arrow-callback': 'error',
|
|
122
|
+
'prefer-template': 'error',
|
|
123
|
+
radix: 'error',
|
|
124
|
+
|
|
125
|
+
// ── rxjs ─────────────────────────────────────────────────────────
|
|
126
|
+
'rxjs/no-implicit-any-catch': ['error', { allowExplicitAny: true }],
|
|
127
|
+
'rxjs/no-sharereplay': 'off',
|
|
128
|
+
|
|
129
|
+
// ── import-x ─────────────────────────────────────────────────────
|
|
130
|
+
'import-x/no-cycle': ['warn', { maxDepth: 3, ignoreExternal: true }],
|
|
131
|
+
'import-x/no-deprecated': 'warn',
|
|
132
|
+
'import-x/first': 'error',
|
|
133
|
+
'import-x/newline-after-import': 'error',
|
|
134
|
+
'import-x/no-duplicates': 'error',
|
|
135
|
+
|
|
136
|
+
// ── simple-import-sort ───────────────────────────────────────────
|
|
137
|
+
'simple-import-sort/imports': 'error',
|
|
138
|
+
'simple-import-sort/exports': 'error',
|
|
9
139
|
},
|
|
10
140
|
},
|
|
11
141
|
];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import tsBase from './ts-base.mjs';
|
|
2
|
+
|
|
3
|
+
// Only the rules written by this library. `tsRecommended` includes it, alongside the
|
|
4
|
+
// house style; use this layer directly to opt out of the latter.
|
|
5
|
+
const tsRules = (plugin) => [
|
|
6
|
+
tsBase(plugin),
|
|
7
|
+
{
|
|
8
|
+
name: 'prestations-ng/ts-rules',
|
|
9
|
+
rules: {
|
|
10
|
+
'@dsivd/prestations-ng/no-direct-signal-mutation': 'error',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
];
|
|
14
|
+
export default tsRules;
|
package/eslint/index.mjs
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import templateRecommended from './configs/template-recommended.mjs';
|
|
2
|
+
import templateRules from './configs/template-rules.mjs';
|
|
2
3
|
import tsRecommended from './configs/ts-recommended.mjs';
|
|
4
|
+
import tsRules from './configs/ts-rules.mjs';
|
|
3
5
|
import rules from './rules/index.mjs';
|
|
4
6
|
|
|
5
7
|
const plugin = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
meta: { name: '@dsivd/prestations-ng' },
|
|
9
|
+
rules,
|
|
8
10
|
};
|
|
9
11
|
|
|
10
12
|
const configs = {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
// The house presets: baselines, this library's rules, and the shared conventions.
|
|
14
|
+
tsRecommended: tsRecommended(plugin),
|
|
15
|
+
templateRecommended: templateRecommended(plugin),
|
|
16
|
+
// Only this library's own rules, for a project that supplies its own style.
|
|
17
|
+
tsRules: tsRules(plugin),
|
|
18
|
+
templateRules: templateRules(plugin),
|
|
13
19
|
};
|
|
14
20
|
|
|
15
21
|
export { configs, plugin, rules };
|
package/package.json
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { builtinRules } from 'eslint/use-at-your-own-risk';
|
|
1
2
|
import { describe, expect, it } from 'vitest';
|
|
2
3
|
|
|
3
4
|
import prestationsNg, { configs, plugin, rules } from '../../index.mjs';
|
|
@@ -6,65 +7,129 @@ const PLUGIN_KEY = '@dsivd/prestations-ng';
|
|
|
6
7
|
|
|
7
8
|
const configNames = Object.keys(configs);
|
|
8
9
|
|
|
9
|
-
//
|
|
10
|
+
// Configs that only register this library's rules, and the presets built on top of them.
|
|
11
|
+
const RULE_LAYERS = ['tsRules', 'templateRules'];
|
|
12
|
+
const PRESETS = [
|
|
13
|
+
['tsRecommended', 'tsRules'],
|
|
14
|
+
['templateRecommended', 'templateRules'],
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const isOff = (severity) =>
|
|
18
|
+
severity === 'off' ||
|
|
19
|
+
severity === 0 ||
|
|
20
|
+
(Array.isArray(severity) && (severity[0] === 'off' || severity[0] === 0));
|
|
21
|
+
|
|
22
|
+
// Rule ids a config turns on. Ids set to 'off' are excluded: ESLint accepts those even
|
|
23
|
+
// when the rule does not exist, which is precisely the escape hatch consumers rely on.
|
|
10
24
|
const enabledIdsOf = (config) =>
|
|
11
|
-
|
|
25
|
+
config.flatMap((entry) =>
|
|
26
|
+
Object.entries(entry.rules ?? {})
|
|
27
|
+
.filter(([, severity]) => !isOff(severity))
|
|
28
|
+
.map(([id]) => id),
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const ownIdsOf = (config) =>
|
|
32
|
+
enabledIdsOf(config).filter((id) => id.startsWith(`${PLUGIN_KEY}/`));
|
|
33
|
+
|
|
34
|
+
const pluginsOf = (config) =>
|
|
35
|
+
Object.assign({}, ...config.map((entry) => entry.plugins ?? {}));
|
|
36
|
+
|
|
37
|
+
// An id is either `plugin-key/rule` (possibly nested, as in `@angular-eslint/template/x`)
|
|
38
|
+
// or a bare core rule.
|
|
39
|
+
const ruleExists = (id, plugins) => {
|
|
40
|
+
for (const [key, declared] of Object.entries(plugins)) {
|
|
41
|
+
if (id.startsWith(`${key}/`)) {
|
|
42
|
+
return Boolean(declared.rules?.[id.slice(key.length + 1)]);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return builtinRules.has(id);
|
|
46
|
+
};
|
|
12
47
|
|
|
13
48
|
describe('eslint configs', () => {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
49
|
+
it('should expose a rule layer and a preset per lint target', () => {
|
|
50
|
+
expect(configNames).toEqual([
|
|
51
|
+
'tsRecommended',
|
|
52
|
+
'templateRecommended',
|
|
53
|
+
'tsRules',
|
|
54
|
+
'templateRules',
|
|
55
|
+
]);
|
|
56
|
+
});
|
|
17
57
|
|
|
18
|
-
|
|
19
|
-
|
|
58
|
+
it.each(RULE_LAYERS)('%s should register the plugin first', (name) => {
|
|
59
|
+
const [base, ...rest] = configs[name];
|
|
20
60
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
61
|
+
expect(base.plugins).toEqual({ [PLUGIN_KEY]: plugin });
|
|
62
|
+
expect(base.rules).toBeUndefined();
|
|
63
|
+
expect(rest.every((entry) => entry.plugins === undefined)).toBe(true);
|
|
64
|
+
});
|
|
25
65
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
// A config referencing a rule that does not exist breaks the lint of every
|
|
33
|
-
// consuming project at once, so it must never reach a release.
|
|
34
|
-
it.each(configNames)('%s should only enable existing rules', (name) => {
|
|
35
|
-
for (const id of enabledIdsOf(configs[name])) {
|
|
36
|
-
expect(id.startsWith(`${PLUGIN_KEY}/`)).toBe(true);
|
|
37
|
-
expect(rules).toHaveProperty(id.slice(PLUGIN_KEY.length + 1));
|
|
38
|
-
}
|
|
39
|
-
});
|
|
66
|
+
it.each(RULE_LAYERS)('%s should carry no third-party rule', (name) => {
|
|
67
|
+
expect(enabledIdsOf(configs[name])).toEqual(ownIdsOf(configs[name]));
|
|
68
|
+
});
|
|
40
69
|
|
|
41
|
-
|
|
42
|
-
|
|
70
|
+
it.each(configNames)('%s should name its own entries', (name) => {
|
|
71
|
+
const own = configs[name].filter((entry) =>
|
|
72
|
+
entry.name?.startsWith('prestations-ng/'),
|
|
73
|
+
);
|
|
43
74
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
});
|
|
75
|
+
expect(own.length).toBeGreaterThan(0);
|
|
76
|
+
});
|
|
48
77
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
78
|
+
// A config referencing a rule that does not exist breaks the lint of every consuming
|
|
79
|
+
// project at once, so it must never reach a release. `builtinRules` is an unstable
|
|
80
|
+
// ESLint export, but it is the only way to check core rule ids.
|
|
81
|
+
it.each(configNames)('%s should only enable existing rules', (name) => {
|
|
82
|
+
const plugins = pluginsOf(configs[name]);
|
|
83
|
+
|
|
84
|
+
for (const id of enabledIdsOf(configs[name])) {
|
|
85
|
+
expect(ruleExists(id, plugins), `unknown rule ${id}`).toBe(true);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Two entries declaring the same plugin key from different copies is a hard ESLint
|
|
90
|
+
// failure, so the preset must register ours exactly once.
|
|
91
|
+
it.each(configNames)('%s should register the plugin once', (name) => {
|
|
92
|
+
const declarations = configs[name]
|
|
93
|
+
.map((entry) => entry.plugins?.[PLUGIN_KEY])
|
|
94
|
+
.filter(Boolean);
|
|
95
|
+
|
|
96
|
+
expect(declarations).toEqual([plugin]);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it.each(PRESETS)('%s should build on %s', (preset, layer) => {
|
|
100
|
+
expect(ownIdsOf(configs[preset])).toEqual(ownIdsOf(configs[layer]));
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('should enable every rule in at least one config', () => {
|
|
104
|
+
const enabled = new Set(configNames.flatMap((n) => ownIdsOf(configs[n])));
|
|
105
|
+
|
|
106
|
+
for (const ruleName of Object.keys(rules)) {
|
|
107
|
+
expect(enabled).toContain(`${PLUGIN_KEY}/${ruleName}`);
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it.each(['tsRecommended', 'tsRules'])(
|
|
112
|
+
'%s should keep the template-only rule out',
|
|
113
|
+
(name) => {
|
|
114
|
+
expect(enabledIdsOf(configs[name])).not.toContain(
|
|
115
|
+
`${PLUGIN_KEY}/no-uninvoked-signal-in-template`,
|
|
116
|
+
);
|
|
117
|
+
},
|
|
52
118
|
);
|
|
53
|
-
});
|
|
54
119
|
});
|
|
55
120
|
|
|
56
121
|
describe('eslint plugin', () => {
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
122
|
+
it('should be named after the npm package', () => {
|
|
123
|
+
expect(plugin.meta.name).toBe(PLUGIN_KEY);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('should mirror the named exports on the default export', () => {
|
|
127
|
+
expect(prestationsNg).toEqual({ configs, plugin, rules });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it.each(Object.entries(rules))('%s should be a usable rule', (_, rule) => {
|
|
131
|
+
expect(typeof rule.create).toBe('function');
|
|
132
|
+
expect(Object.keys(rule.meta.messages).length).toBeGreaterThan(0);
|
|
133
|
+
expect(rule.meta.type).toBe('problem');
|
|
134
|
+
});
|
|
70
135
|
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Registers the plugin without enabling any rule. Meant to be used through
|
|
2
|
-
// `templateRecommended`, inside the `extends` of a block that
|
|
3
|
-
// `**/*.html` and sets the Angular template parser.
|
|
2
|
+
// `templateRules` or `templateRecommended`, inside the `extends` of a block that
|
|
3
|
+
// already targets `**/*.html` and sets the Angular template parser.
|
|
4
4
|
const templateBase = (plugin) => ({
|
|
5
5
|
name: 'prestations-ng/template-base',
|
|
6
6
|
plugins: {
|
|
@@ -1,14 +1,23 @@
|
|
|
1
|
-
import
|
|
1
|
+
import angular from 'angular-eslint';
|
|
2
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
|
+
*/
|
|
3
12
|
const templateRecommended = (plugin) => [
|
|
4
|
-
|
|
13
|
+
...angular.configs.templateRecommended,
|
|
14
|
+
...angular.configs.templateAccessibility,
|
|
15
|
+
...templateRules(plugin),
|
|
5
16
|
{
|
|
6
17
|
name: 'prestations-ng/template-recommended',
|
|
7
18
|
rules: {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
'@dsivd/prestations-ng/no-direct-signal-mutation': 'error',
|
|
11
|
-
'@dsivd/prestations-ng/no-uninvoked-signal-in-template': 'error',
|
|
19
|
+
'@angular-eslint/template/no-negated-async': 'off',
|
|
20
|
+
'@angular-eslint/template/button-has-type': 'error',
|
|
12
21
|
},
|
|
13
22
|
},
|
|
14
23
|
];
|
|
@@ -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;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Registers the plugin without enabling any rule. Meant to be used through
|
|
2
|
-
// `tsRecommended`, inside the `extends` of a block that already
|
|
3
|
-
// and sets the TypeScript parser.
|
|
2
|
+
// `tsRules` or `tsRecommended`, inside the `extends` of a block that already
|
|
3
|
+
// targets `**/*.ts` and sets the TypeScript parser.
|
|
4
4
|
const tsBase = (plugin) => ({
|
|
5
5
|
name: 'prestations-ng/ts-base',
|
|
6
6
|
plugins: {
|
|
@@ -1,11 +1,141 @@
|
|
|
1
|
-
import
|
|
1
|
+
import eslint from '@eslint/js';
|
|
2
|
+
import rxjs from '@smarttools/eslint-plugin-rxjs';
|
|
3
|
+
import angular from 'angular-eslint';
|
|
4
|
+
import importX from 'eslint-plugin-import-x';
|
|
5
|
+
import simpleImportSort from 'eslint-plugin-simple-import-sort';
|
|
6
|
+
import tseslint from 'typescript-eslint';
|
|
2
7
|
|
|
8
|
+
import tsRules from './ts-rules.mjs';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The house TypeScript preset: the shared baselines, this library's own rules, and the
|
|
12
|
+
* conventions every prestations-ng project follows.
|
|
13
|
+
*
|
|
14
|
+
* Meant for the `extends` of a `**\/*.ts` block. It brings its own parser and plugins,
|
|
15
|
+
* so do not spread `angular.configs.tsRecommended` alongside it — declaring the same
|
|
16
|
+
* plugin twice from two different copies makes ESLint fail outright.
|
|
17
|
+
*/
|
|
3
18
|
const tsRecommended = (plugin) => [
|
|
4
|
-
|
|
19
|
+
eslint.configs.recommended,
|
|
20
|
+
...tseslint.configs.recommended, // covers: no-explicit-any, ban-ts-comment,
|
|
21
|
+
// no-unused-expressions, no-var, no-unused-vars…
|
|
22
|
+
...tseslint.configs.stylistic, // covers: array-type, consistent-type-assertions,
|
|
23
|
+
// dot-notation, no-inferrable-types (default options),
|
|
24
|
+
// no-empty-function (error), prefer-for-of,
|
|
25
|
+
// prefer-function-type…
|
|
26
|
+
...angular.configs.tsRecommended, // covers: prefer-inject, no-empty-lifecycle-method,
|
|
27
|
+
// use-lifecycle-interface…
|
|
28
|
+
...tsRules(plugin),
|
|
5
29
|
{
|
|
6
30
|
name: 'prestations-ng/ts-recommended',
|
|
31
|
+
plugins: {
|
|
32
|
+
rxjs,
|
|
33
|
+
'simple-import-sort': simpleImportSort,
|
|
34
|
+
'import-x': importX,
|
|
35
|
+
},
|
|
7
36
|
rules: {
|
|
8
|
-
|
|
37
|
+
// ── @angular-eslint ──────────────────────────────────────────────
|
|
38
|
+
'@angular-eslint/component-selector': [
|
|
39
|
+
'error',
|
|
40
|
+
{
|
|
41
|
+
type: 'element',
|
|
42
|
+
prefix: 'app',
|
|
43
|
+
style: 'kebab-case',
|
|
44
|
+
},
|
|
45
|
+
],
|
|
46
|
+
'@angular-eslint/directive-selector': [
|
|
47
|
+
'error',
|
|
48
|
+
{
|
|
49
|
+
type: 'attribute',
|
|
50
|
+
prefix: 'app',
|
|
51
|
+
style: 'camelCase',
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
'@angular-eslint/no-uncalled-signals': ['error'],
|
|
55
|
+
'@angular-eslint/prefer-signal-model': ['error'],
|
|
56
|
+
'@angular-eslint/prefer-signals': ['error'],
|
|
57
|
+
|
|
58
|
+
// ── @typescript-eslint ───────────────────────────────────────────
|
|
59
|
+
'@typescript-eslint/explicit-function-return-type': [
|
|
60
|
+
'error',
|
|
61
|
+
{
|
|
62
|
+
allowExpressions: true,
|
|
63
|
+
allowTypedFunctionExpressions: true,
|
|
64
|
+
allowHigherOrderFunctions: true,
|
|
65
|
+
allowDirectConstAssertionInArrowFunctions: true,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
'@typescript-eslint/explicit-member-accessibility': [
|
|
69
|
+
'error',
|
|
70
|
+
{ accessibility: 'no-public' },
|
|
71
|
+
],
|
|
72
|
+
'@typescript-eslint/member-ordering': 'error',
|
|
73
|
+
'@typescript-eslint/no-base-to-string': 'warn',
|
|
74
|
+
// stylistic enables no-inferrable-types with default options (ignoreParameters: false);
|
|
75
|
+
// override here to allow typed parameters explicitly
|
|
76
|
+
'@typescript-eslint/no-inferrable-types': [
|
|
77
|
+
'error',
|
|
78
|
+
{ ignoreParameters: true },
|
|
79
|
+
],
|
|
80
|
+
// stylistic sets no-empty-function to error; downgrade to warn
|
|
81
|
+
'@typescript-eslint/no-empty-function': 'warn',
|
|
82
|
+
'@typescript-eslint/prefer-includes': 'warn',
|
|
83
|
+
'@typescript-eslint/return-await': ['error', 'never'],
|
|
84
|
+
'@typescript-eslint/typedef': ['error', { parameter: true }],
|
|
85
|
+
'@typescript-eslint/unified-signatures': 'error',
|
|
86
|
+
// off here — @typescript-eslint/no-shadow handles it correctly for TS
|
|
87
|
+
'no-shadow': 'off',
|
|
88
|
+
'@typescript-eslint/no-shadow': ['error', { hoist: 'all' }],
|
|
89
|
+
'@typescript-eslint/no-unused-vars': [
|
|
90
|
+
'error',
|
|
91
|
+
{
|
|
92
|
+
varsIgnorePattern: '^_',
|
|
93
|
+
argsIgnorePattern: '^_',
|
|
94
|
+
caughtErrorsIgnorePattern: '^_',
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
|
|
98
|
+
// ── eslint core ──────────────────────────────────────────────────
|
|
99
|
+
'arrow-body-style': 'error',
|
|
100
|
+
'arrow-parens': ['error', 'as-needed'],
|
|
101
|
+
curly: 'error',
|
|
102
|
+
eqeqeq: ['error', 'always'],
|
|
103
|
+
'guard-for-in': 'error',
|
|
104
|
+
'no-bitwise': 'error',
|
|
105
|
+
'no-caller': 'error',
|
|
106
|
+
'no-console': ['error', { allow: ['log', 'warn', 'error'] }],
|
|
107
|
+
'no-eval': 'error',
|
|
108
|
+
'no-extra-boolean-cast': 'off',
|
|
109
|
+
'no-multiple-empty-lines': 'error',
|
|
110
|
+
'no-new-wrappers': 'error',
|
|
111
|
+
'no-restricted-imports': [
|
|
112
|
+
'error',
|
|
113
|
+
{ paths: ['rxjs/Rx', 'primeng/primeng', 'primeng'] },
|
|
114
|
+
],
|
|
115
|
+
'no-return-assign': 'error',
|
|
116
|
+
'no-throw-literal': 'error',
|
|
117
|
+
'no-undef-init': 'error',
|
|
118
|
+
'no-useless-concat': 'error',
|
|
119
|
+
'object-shorthand': ['error', 'always', { avoidQuotes: true }],
|
|
120
|
+
'one-var': ['error', 'never'],
|
|
121
|
+
'prefer-arrow-callback': 'error',
|
|
122
|
+
'prefer-template': 'error',
|
|
123
|
+
radix: 'error',
|
|
124
|
+
|
|
125
|
+
// ── rxjs ─────────────────────────────────────────────────────────
|
|
126
|
+
'rxjs/no-implicit-any-catch': ['error', { allowExplicitAny: true }],
|
|
127
|
+
'rxjs/no-sharereplay': 'off',
|
|
128
|
+
|
|
129
|
+
// ── import-x ─────────────────────────────────────────────────────
|
|
130
|
+
'import-x/no-cycle': ['warn', { maxDepth: 3, ignoreExternal: true }],
|
|
131
|
+
'import-x/no-deprecated': 'warn',
|
|
132
|
+
'import-x/first': 'error',
|
|
133
|
+
'import-x/newline-after-import': 'error',
|
|
134
|
+
'import-x/no-duplicates': 'error',
|
|
135
|
+
|
|
136
|
+
// ── simple-import-sort ───────────────────────────────────────────
|
|
137
|
+
'simple-import-sort/imports': 'error',
|
|
138
|
+
'simple-import-sort/exports': 'error',
|
|
9
139
|
},
|
|
10
140
|
},
|
|
11
141
|
];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import tsBase from './ts-base.mjs';
|
|
2
|
+
|
|
3
|
+
// Only the rules written by this library. `tsRecommended` includes it, alongside the
|
|
4
|
+
// house style; use this layer directly to opt out of the latter.
|
|
5
|
+
const tsRules = (plugin) => [
|
|
6
|
+
tsBase(plugin),
|
|
7
|
+
{
|
|
8
|
+
name: 'prestations-ng/ts-rules',
|
|
9
|
+
rules: {
|
|
10
|
+
'@dsivd/prestations-ng/no-direct-signal-mutation': 'error',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
];
|
|
14
|
+
export default tsRules;
|
package/src/eslint/index.mjs
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
import templateRecommended from './configs/template-recommended.mjs';
|
|
2
|
+
import templateRules from './configs/template-rules.mjs';
|
|
2
3
|
import tsRecommended from './configs/ts-recommended.mjs';
|
|
4
|
+
import tsRules from './configs/ts-rules.mjs';
|
|
3
5
|
import rules from './rules/index.mjs';
|
|
4
6
|
|
|
5
7
|
const plugin = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
meta: { name: '@dsivd/prestations-ng' },
|
|
9
|
+
rules,
|
|
8
10
|
};
|
|
9
11
|
|
|
10
12
|
const configs = {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
// The house presets: baselines, this library's rules, and the shared conventions.
|
|
14
|
+
tsRecommended: tsRecommended(plugin),
|
|
15
|
+
templateRecommended: templateRecommended(plugin),
|
|
16
|
+
// Only this library's own rules, for a project that supplies its own style.
|
|
17
|
+
tsRules: tsRules(plugin),
|
|
18
|
+
templateRules: templateRules(plugin),
|
|
13
19
|
};
|
|
14
20
|
|
|
15
21
|
export { configs, plugin, rules };
|
|
Binary file
|