@dsivd/prestations-ng 19.1.1 → 19.2.0-beta.1

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 CHANGED
@@ -6,6 +6,13 @@
6
6
 
7
7
  ---
8
8
 
9
+ ## [19.2.0] - should be aligned with prestations-be 19.2.x
10
+
11
+ ### Changed
12
+
13
+ Library now runs as strict mode, which may cause some errors to appear in your project after updating.
14
+ Please check the [UPGRADING_V19.md](UPGRADING_V19.md) guide for more information.
15
+
9
16
  ## [19.1.1]
10
17
 
11
18
  ### Fixed
@@ -13,12 +20,12 @@
13
20
  - [foehn-input-phone.component.ts](projects/prestations-ng/src/foehn-input/foehn-input-phone.component.ts)
14
21
  - fixed help texts to be aligned with ch.vd.cyber.prestationsbe.validation.PhoneNumberValidator (prestations-be)
15
22
  - dictionary keys have changed to be more explicit :
16
- - foehn-input-phone.STANDARD_HELPTEXT_SWISS_FIX_AND_MOBILE
17
- - foehn-input-phone.STANDARD_HELPTEXT_SWISS_FIX_ONLY
18
- - foehn-input-phone.STANDARD_HELPTEXT_SWISS_MOBILE_ONLY
19
- - foehn-input-phone.STANDARD_HELPTEXT_INTERNATIONAL_FIX_AND_MOBILE
20
- - foehn-input-phone.STANDARD_HELPTEXT_INTERNATIONAL_FIX_ONLY
21
- - foehn-input-phone.STANDARD_HELPTEXT_INTERNATIONAL_MOBILE_ONLY
23
+ - foehn-input-phone.STANDARD_HELPTEXT_SWISS_FIX_AND_MOBILE
24
+ - foehn-input-phone.STANDARD_HELPTEXT_SWISS_FIX_ONLY
25
+ - foehn-input-phone.STANDARD_HELPTEXT_SWISS_MOBILE_ONLY
26
+ - foehn-input-phone.STANDARD_HELPTEXT_INTERNATIONAL_FIX_AND_MOBILE
27
+ - foehn-input-phone.STANDARD_HELPTEXT_INTERNATIONAL_FIX_ONLY
28
+ - foehn-input-phone.STANDARD_HELPTEXT_INTERNATIONAL_MOBILE_ONLY
22
29
 
23
30
  ## [19.1.0] - should be aligned with prestations-be 19.1.x
24
31
 
package/GOOD_PRACTICES.md CHANGED
@@ -95,29 +95,25 @@ Ce qu'il ne faut **pas** faire :
95
95
 
96
96
  ```html
97
97
  <h1>...</h1>
98
- <h3>...</h3>
99
- <h2>...</h2>
100
- <h6>...</h6>
101
- ...
102
- ...
103
- ...
98
+ <h3>...</h3>
99
+ <h2>...</h2>
100
+ <h6>...</h6>
101
+ ... ... ...
104
102
  ```
105
103
 
106
104
  Ce qu'il **faut** faire :
107
105
 
108
106
  ```html
109
107
  <h1>...</h1>
110
- <section>
111
- <h2>...</h2>
112
- <h3>...</h3>
113
- <h4>...</h4>
114
- ...
115
- ...
116
- </section>
117
- <h2>...</h2>
118
- ...
119
- <section>
120
- ...
108
+ <section>
109
+ <h2>...</h2>
110
+ <h3>...</h3>
111
+ <h4>...</h4>
112
+ ... ...
113
+ </section>
114
+ <h2>...</h2>
115
+ ...
116
+ <section>...</section>
121
117
  ```
122
118
 
123
119
  Quelques attributs HTML utiles :
@@ -216,32 +212,36 @@ Exemple :
216
212
 
217
213
  ```html
218
214
  <ul>
219
- <li>un artichaut</li>
220
- <li>Les trucs pour le gâteau
221
- <!-- On voit que </li> n'est pas là -->
222
- <ul>
223
- <li>trois œufs</li>
224
- <li>La génoise
225
- <!-- Là on ouvre une autre liste -->
215
+ <li>un artichaut</li>
216
+ <li>
217
+ Les trucs pour le gâteau
218
+ <!-- On voit que </li> n'est pas là -->
226
219
  <ul>
227
- <li>100g de sucre</li>
228
- <li>un œuf</li>
229
- <li>150g de farine</li>
220
+ <li>trois œufs</li>
221
+ <li>
222
+ La génoise
223
+ <!-- Là on ouvre une autre liste -->
224
+ <ul>
225
+ <li>100g de sucre</li>
226
+ <li>un œuf</li>
227
+ <li>150g de farine</li>
228
+ </ul>
229
+ </li>
230
+ <!-- On ferme la liste la plus imbriquée -->
231
+ <li>200g de chocolat</li>
230
232
  </ul>
231
- </li> <!-- On ferme la liste la plus imbriquée -->
232
- <li>200g de chocolat</li>
233
- </ul>
234
- <!-- On ferme la liste imbriquée avec </li> -->
235
- </li>
236
- <li>De l'essuie-tout</li>
237
- <li>A faire dans l'ordre pour une belle vaisselle
238
- <ol>
239
- <li>Remplir le lave vaisselle</li>
240
- <li>Mettre une pastille pour lave vaisselle</li>
241
- <li>Fermer la porte du lave vaisselle</li>
242
- <li>Lancer le lave vaisselle</li>
243
- </ol>
244
- </li>
233
+ <!-- On ferme la liste imbriquée avec </li> -->
234
+ </li>
235
+ <li>De l'essuie-tout</li>
236
+ <li>
237
+ A faire dans l'ordre pour une belle vaisselle
238
+ <ol>
239
+ <li>Remplir le lave vaisselle</li>
240
+ <li>Mettre une pastille pour lave vaisselle</li>
241
+ <li>Fermer la porte du lave vaisselle</li>
242
+ <li>Lancer le lave vaisselle</li>
243
+ </ol>
244
+ </li>
245
245
  </ul>
246
246
  ```
247
247
 
@@ -262,18 +262,18 @@ Exemple :
262
262
 
263
263
  ```html
264
264
  <form>
265
- <fieldset>
266
- <legend>Choose your favorite monster</legend>
265
+ <fieldset>
266
+ <legend>Choose your favorite monster</legend>
267
267
 
268
- <input type="radio" id="kraken" name="monster" value="K" />
269
- <label for="kraken">Kraken</label><br />
268
+ <input type="radio" id="kraken" name="monster" value="K" />
269
+ <label for="kraken">Kraken</label><br />
270
270
 
271
- <input type="radio" id="sasquatch" name="monster" value="S" />
272
- <label for="sasquatch">Sasquatch</label><br />
271
+ <input type="radio" id="sasquatch" name="monster" value="S" />
272
+ <label for="sasquatch">Sasquatch</label><br />
273
273
 
274
- <input type="radio" id="mothman" name="monster" value="M" />
275
- <label for="mothman">Mothman</label>
276
- </fieldset>
274
+ <input type="radio" id="mothman" name="monster" value="M" />
275
+ <label for="mothman">Mothman</label>
276
+ </fieldset>
277
277
  </form>
278
278
  ```
279
279
 
@@ -331,7 +331,10 @@ avec du texte pour les non-voyants.
331
331
  // ... vos composants foehn-* utilisés dans le template
332
332
  ],
333
333
  })
334
- export class PageOneComponent extends AbstractPageComponent<BusinessForm> implements OnInit {
334
+ export class PageOneComponent
335
+ extends AbstractPageComponent<BusinessForm>
336
+ implements OnInit
337
+ {
335
338
  ngOnInit(): void {
336
339
  super.ngOnInit();
337
340
  // point (2) : ajoutera le titre h1
@@ -361,7 +364,10 @@ export class PageOneComponent extends AbstractPageComponent<BusinessForm> implem
361
364
  <div class="row">
362
365
  <div class="col-md-8">
363
366
  <!-- point (5) : toujours avoir un <h2> en premier dans votre page -->
364
- <h2>{{ 'La_clé_du_dictionnaire_de_votre_titre_H2_ici' | fromDictionary }}</h2>
367
+ <h2>
368
+ {{ 'La_clé_du_dictionnaire_de_votre_titre_H2_ici' |
369
+ fromDictionary }}
370
+ </h2>
365
371
 
366
372
  <!-- [VOS COMPOSANTS FOEHN ICI] -->
367
373
 
@@ -390,13 +396,21 @@ export const routes: Routes = [
390
396
  component: PageWrapperComponent,
391
397
  data: { root: true },
392
398
  children: [
393
- { path: '', component: PageOneComponent, canActivate: [GesdemLoaderGuard] },
399
+ {
400
+ path: '',
401
+ component: PageOneComponent,
402
+ canActivate: [GesdemLoaderGuard],
403
+ },
394
404
  { path: '404', component: FoehnNotfoundComponent },
395
405
  { path: 'erreur', component: GesdemErrorComponent },
396
406
  {
397
407
  path: ':reference',
398
408
  children: [
399
- { path: 'page-1', component: PageOneComponent, data: { order: 1 } },
409
+ {
410
+ path: 'page-1',
411
+ component: PageOneComponent,
412
+ data: { order: 1 },
413
+ },
400
414
  // {...},
401
415
  // ...
402
416
  { path: 'erreur', component: GesdemErrorComponent },
@@ -431,7 +445,9 @@ export const routes: Routes = [
431
445
  {
432
446
  path: 'welcome',
433
447
  loadChildren: () =>
434
- import('./modules/welcome/welcome.routes').then((m) => m.welcome_routes),
448
+ import('./modules/welcome/welcome.routes').then(
449
+ (m) => m.welcome_routes,
450
+ ),
435
451
  },
436
452
  {
437
453
  path: 'inscription',
@@ -443,7 +459,11 @@ export const routes: Routes = [
443
459
  // {...},
444
460
  // ...
445
461
  // recovery link should redirect to /inscription/reference
446
- { path: ':reference', redirectTo: 'inscription/:reference', pathMatch: 'full' },
462
+ {
463
+ path: ':reference',
464
+ redirectTo: 'inscription/:reference',
465
+ pathMatch: 'full',
466
+ },
447
467
  { path: '', redirectTo: 'welcome', pathMatch: 'full' },
448
468
  { path: '404', component: FoehnNotfoundComponent },
449
469
  { path: '**', redirectTo: '/404', pathMatch: 'full' },
@@ -475,12 +495,20 @@ export const inscription_routes: Routes = [
475
495
  path: '',
476
496
  data: { root: true },
477
497
  children: [
478
- { path: '', component: PageOneComponent, canActivate: [GesdemLoaderGuard] },
498
+ {
499
+ path: '',
500
+ component: PageOneComponent,
501
+ canActivate: [GesdemLoaderGuard],
502
+ },
479
503
  { path: 'erreur', component: GesdemErrorComponent },
480
504
  {
481
505
  path: ':reference',
482
506
  children: [
483
- { path: 'page-1', component: PageOneComponent, data: { order: 1 } },
507
+ {
508
+ path: 'page-1',
509
+ component: PageOneComponent,
510
+ data: { order: 1 },
511
+ },
484
512
  // {...},
485
513
  // ...
486
514
  { path: 'erreur', component: GesdemErrorComponent },
@@ -599,7 +627,11 @@ export class InputDateComponent extends FoehnInputComponent<number[]> {
599
627
 
600
628
  // point (8)
601
629
  getValidValue(): number[] | null | undefined {
602
- if (this.isEmpty(this.day) && this.isEmpty(this.month) && this.isEmpty(this.year)) {
630
+ if (
631
+ this.isEmpty(this.day) &&
632
+ this.isEmpty(this.month) &&
633
+ this.isEmpty(this.year)
634
+ ) {
603
635
  if (this.model_ !== undefined) {
604
636
  return null;
605
637
  }
@@ -607,7 +639,11 @@ export class InputDateComponent extends FoehnInputComponent<number[]> {
607
639
  return undefined;
608
640
  }
609
641
 
610
- return [this.toNumber(this.year), this.toNumber(this.month), this.toNumber(this.day)];
642
+ return [
643
+ this.toNumber(this.year),
644
+ this.toNumber(this.month),
645
+ this.toNumber(this.day),
646
+ ];
611
647
  }
612
648
 
613
649
  // ...code
@@ -694,15 +730,17 @@ Chaque `foehn-input-*` utilisé dans votre vue doit :
694
730
  >
695
731
  <!-- (facultatif) Seulement si vous souhaitez afficher un label/legend global à votre composant -->
696
732
  @if (!!label()) {
697
- <legend
698
- [class.visually-hidden]="isLabelSrOnly()"
699
- [class.vd-p]="!isLabelSrOnly()"
733
+ <legend
734
+ [class.visually-hidden]="isLabelSrOnly()"
735
+ [class.vd-p]="!isLabelSrOnly()"
736
+ >
737
+ <span [innerHTML]="label()"></span>
738
+ @if (!required() && !hideNotRequiredExtraLabel()) {
739
+ <span aria-hidden="true"
740
+ >{{ 'foehn-input.optional' | fromDictionary }}</span
700
741
  >
701
- <span [innerHTML]="label()"></span>
702
- @if (!required() && !hideNotRequiredExtraLabel()) {
703
- <span aria-hidden="true">{{ 'foehn-input.optional' | fromDictionary }}</span>
704
- }
705
- </legend>
742
+ }
743
+ </legend>
706
744
  }
707
745
 
708
746
  <!-- (obligatoire) -->
@@ -710,11 +748,11 @@ Chaque `foehn-input-*` utilisé dans votre vue doit :
710
748
 
711
749
  <!-- (facultatif) Seulement si vous souhaitez pouvoir afficher un helpText global au composant -->
712
750
  @if (helpText()) {
713
- <small
714
- [attr.id]="buildId() + 'Help'"
715
- class="text-secondary"
716
- [innerHTML]="helpText()"
717
- ></small>
751
+ <small
752
+ [attr.id]="buildId() + 'Help'"
753
+ class="text-secondary"
754
+ [innerHTML]="helpText()"
755
+ ></small>
718
756
  }
719
757
 
720
758
  <!-- ...vos foehn-input-* ici -->
@@ -769,13 +807,18 @@ Voici un exemple de page de formulaire contenant cette méthode (1) :
769
807
  // ... vos composants foehn-*
770
808
  ],
771
809
  })
772
- export class VotrePageComponent extends AbstractPageComponent<BusinessForm> implements OnInit {
810
+ export class VotrePageComponent
811
+ extends AbstractPageComponent<BusinessForm>
812
+ implements OnInit
813
+ {
773
814
  // vos propres dépendances : inject() au lieu du constructor
774
815
  private readonly metaService = inject(MetaService);
775
816
 
776
817
  ngOnInit(): void {
777
818
  super.ngOnInit();
778
- this.setPageTitle(this._dictionaryService.getKeySync('votre-page.title'));
819
+ this.setPageTitle(
820
+ this._dictionaryService.getKeySync('votre-page.title'),
821
+ );
779
822
  }
780
823
 
781
824
  newForm(): BusinessForm {
@@ -889,37 +932,38 @@ devra lui aussi remonter l'événement.
889
932
  tabindex="-1"
890
933
  >
891
934
  @if (label() && type() !== 'hidden') {
892
- <label
893
- [attr.for]="buildChildId()"
894
- [class]="'form-label ' + (isLabelSrOnly() ? 'visually-hidden' : (labelStyleModifier() ?? ''))"
935
+ <label
936
+ [attr.for]="buildChildId()"
937
+ [class]="'form-label ' + (isLabelSrOnly() ? 'visually-hidden' : (labelStyleModifier() ?? ''))"
938
+ >
939
+ {{ label() }} @if (!required() && !hideNotRequiredExtraLabel()) {
940
+ <span aria-hidden="true"
941
+ >{{ 'foehn-input.optional' | fromDictionary }}</span
895
942
  >
896
- {{ label() }}
897
- @if (!required() && !hideNotRequiredExtraLabel()) {
898
- <span aria-hidden="true">{{ 'foehn-input.optional' | fromDictionary }}</span>
899
- }
900
- </label>
943
+ }
944
+ </label>
901
945
  }
902
946
 
903
947
  <foehn-validation-alerts [component]="this" />
904
948
 
905
949
  @if (helpText()) {
906
- <small
907
- [attr.id]="buildChildId() + 'Help'"
908
- class="form-text text-secondary"
909
- [innerHTML]="helpText()"
910
- ></small>
950
+ <small
951
+ [attr.id]="buildChildId() + 'Help'"
952
+ class="form-text text-secondary"
953
+ [innerHTML]="helpText()"
954
+ ></small>
911
955
  }
912
956
 
913
957
  <!-- (1) votre boucle @for + track (identifiant unique) -->
914
958
  @for (vehicule of vehicules; track vehicule.id; let i = $index) {
915
- <!-- (2) votre composant réutilisable -->
916
- <app-vehicule-item
917
- [name]="name() + '[' + i + ']'"
918
- [(model)]="vehicule"
919
- (modelChange)="updateVehiculesList($event, i)"
920
- (userInput)="handleUserInput($event, i)"
921
- (removedVehicule)="handleRemovedVehicule(i)"
922
- />
959
+ <!-- (2) votre composant réutilisable -->
960
+ <app-vehicule-item
961
+ [name]="name() + '[' + i + ']'"
962
+ [(model)]="vehicule"
963
+ (modelChange)="updateVehiculesList($event, i)"
964
+ (userInput)="handleUserInput($event, i)"
965
+ (removedVehicule)="handleRemovedVehicule(i)"
966
+ />
923
967
  }
924
968
 
925
969
  <button class="btn btn-primary mt-2" (click)="addVehicule()">
@@ -992,7 +1036,11 @@ l'événement de suppression (2).
992
1036
  />
993
1037
  <!-- (1) Utilisation de votre liste - (2) Appel de la méthode qui sauvegardera les données dans GESDEM afin de rafraîchir les erreurs -->
994
1038
 
995
- <foehn-navigation id="navigation" (onPrevious)="previous()" (onNext)="send()" />
1039
+ <foehn-navigation
1040
+ id="navigation"
1041
+ (onPrevious)="previous()"
1042
+ (onNext)="send()"
1043
+ />
996
1044
  </foehn-form>
997
1045
  ```
998
1046
 
@@ -1003,7 +1051,10 @@ formulaire et ainsi rafraîchir l'index.
1003
1051
  @Component({
1004
1052
  // ...
1005
1053
  })
1006
- export class DemandePageComponent extends AbstractPageComponent<BusinessForm> implements OnInit {
1054
+ export class DemandePageComponent
1055
+ extends AbstractPageComponent<BusinessForm>
1056
+ implements OnInit
1057
+ {
1007
1058
  // ...code
1008
1059
 
1009
1060
  handleRemovedVehicule(): void {
@@ -1066,10 +1117,7 @@ export class BusinessForm {
1066
1117
  name="identification.lastName"
1067
1118
  />
1068
1119
 
1069
- <foehn-boolean-checkbox
1070
- [(model)]="form.acceptCookies"
1071
- name="acceptCookies"
1072
- />
1120
+ <foehn-boolean-checkbox [(model)]="form.acceptCookies" name="acceptCookies" />
1073
1121
  <!-- 1 niveau de profondeur -->
1074
1122
  ```
1075
1123
 
package/UPGRADING_V18.md CHANGED
@@ -458,6 +458,7 @@ npm install @angular/cli@19 -g
458
458
  ng update typescript@5.5
459
459
  ng update @dsivd/prestations-ng@18
460
460
  ```
461
+
461
462
  NB: You might need to use the --force option.
462
463
 
463
464
  When asked the question "Select the migrations that you'd like to run", accept "provide-initializer" by hitting "Space" key then "Enter".
package/UPGRADING_V19.md CHANGED
@@ -11,6 +11,9 @@ From here on, we are in the root folder of your project, for example ~/git/cybsd
11
11
 
12
12
  ### Spring Boot 4
13
13
 
14
+ NB : if the following instructions (jdk25 + latest parent + openrewrite) are not working fine, you can also try to
15
+ do in the reverse order : in jdk21 do the rewrite commands, then switch to jdk25 and latest parent.
16
+
14
17
  You can switch to Java 25 `jdk25` + change in IntelliJ: right-click on your project > Open Module Settings > Project > set "SDK" to `jdk25` from devtools and "Language
15
18
  level" to `25` in "Modules"
16
19
 
@@ -78,6 +81,17 @@ if you use ldap, you should replace `spring-ldap-core` by `spring-boot-starter-l
78
81
  </dependency>
79
82
  ```
80
83
 
84
+ ### ORACLE
85
+
86
+ if you use oracle, you should replace `ojdbc10` by `ojdbc17` in your `pom.xml` and let the spring boot parent set the version
87
+
88
+ ```xml
89
+ <dependency>
90
+ <groupId>com.oracle.database.jdbc</groupId>
91
+ <artifactId>ojdbc17</artifactId>
92
+ </dependency>
93
+ ```
94
+
81
95
  ### Jackson migration
82
96
 
83
97
  With Spring Boot 4, Jackson 3 is now used instead of Jackson 2, so we need to replace the previous Jackson 2 classes.
@@ -857,7 +871,7 @@ ng generate @angular/core:signal-input-migration --insert-todos
857
871
  [Angular migration guide: migrating to Vitest](https://angular.dev/guide/testing/migrating-to-vitest)
858
872
 
859
873
  ```bash
860
- npm install --save-dev vitest jsdom
874
+ npm install --save-dev vitest@4.0 jsdom
861
875
  ```
862
876
 
863
877
  In your `angular.json` file, remove `"zone.js/dist/zone-error"` from `"allowedCommonJsDependencies"`
@@ -1396,7 +1410,7 @@ npm uninstall @angular/animations
1396
1410
 
1397
1411
  ### Add missing `^` or `~` in `package.json` devDependencies
1398
1412
 
1399
- Check you devDependencies in your `package.json` file and add `^` in front of every version number if missing excepted for `typescript` where you should add `~`. Then run:
1413
+ Check you devDependencies in your `package.json` file and add `^` in front of every version number if missing excepted for `typescript` and `vitest` where you should add `~`. Then run:
1400
1414
 
1401
1415
  ```bash
1402
1416
  npm install
@@ -1416,6 +1430,25 @@ When using `IntelliJ`, you can run code inspection on your project to fix deprec
1416
1430
  - IntelliJ window menu, go to: `Code > Analyze code > Run inspection by name...` (or **Ctrl+Alt+Maj+i**) and type `Deprecated symbol used`.
1417
1431
  - Or select your project in your project panel and `right click` on it then `Analyze > Run inspection by name...` (or **Ctrl+Alt+Maj+i**) and type `Deprecated symbol used`.
1418
1432
 
1433
+ ### Strict mode (optional when migrating large projects but recommended)
1434
+
1435
+ - In your `tsconfig.json` file, add `"strict": true` in `compilerOptions` and remove `alwaysStrict`, `noImplicitThis`, `strictBindCallApply` and `strictFunctionTypes` as they are already included in `strict` mode.
1436
+ - In your `tsconfig.json` file, remove `useDefineForClassFields` from `compilerOptions`.
1437
+
1438
+ Now fix your project until no more errors:
1439
+
1440
+ ```bash
1441
+ npx tsc -b --noEmit
1442
+ ```
1443
+
1444
+ and finally run the following commands to build, test and start your application:
1445
+
1446
+ ```bash
1447
+ npm run build
1448
+ npm run test
1449
+ npm start
1450
+ ```
1451
+
1419
1452
  ### Last but not least, check if your application is working!!!
1420
1453
 
1421
1454
  #### Be sure your application gets a fresh start
@@ -1465,3 +1498,11 @@ See [getImtPortailApplications](https://dsigit.etat-de-vaud.ch/outils/git/projec
1465
1498
 
1466
1499
  Enroll your project in Renovate bot to automatically update your dependencies.
1467
1500
  See [Mend Renovate CE](https://dgnsiwiki.etat-de-vaud.ch/outils/dgnsiwiki/x/7QFDgg) for more information.
1501
+
1502
+ update renovate.json :
1503
+
1504
+ ```json
1505
+ {
1506
+ "extends": ["local>PEE/renovate-config"]
1507
+ }
1508
+ ```
@@ -153,6 +153,67 @@ const reExportedModulesOf = (sourceFile) =>
153
153
  .filter((statement) => ts.isExportDeclaration(statement) && statement.moduleSpecifier)
154
154
  .map((statement) => statement.moduleSpecifier.text);
155
155
 
156
+ const classDeclarationsOf = (sourceFile, className) =>
157
+ className
158
+ ? [findClass(sourceFile, className)].filter(Boolean)
159
+ : sourceFile.statements.filter(ts.isClassDeclaration);
160
+
161
+ const isBarrelMiss = (className, classDeclarations) =>
162
+ Boolean(className) && classDeclarations.length === 0;
163
+
164
+ const collectFromReExports = (sourceFile, path, className, kinds, visited) => {
165
+ for (const specifier of reExportedModulesOf(sourceFile)) {
166
+ const target = resolveModule(specifier, path);
167
+ if (target) {
168
+ collect(target, className, kinds, visited);
169
+ }
170
+ }
171
+ };
172
+
173
+ const addMemberKindIfEligible = (member, kinds) => {
174
+ if (!member.name || !ts.isIdentifier(member.name)) {
175
+ return;
176
+ }
177
+ const kind = kindOfMember(member);
178
+ // A subclass overriding a member wins: it is collected first.
179
+ if (kind && !kinds.has(member.name.text)) {
180
+ kinds.set(member.name.text, kind);
181
+ }
182
+ };
183
+
184
+ const collectMemberKinds = (classDeclaration, kinds) => {
185
+ for (const member of classDeclaration.members) {
186
+ addMemberKindIfEligible(member, kinds);
187
+ }
188
+ };
189
+
190
+ const baseTargetOf = (sourceFile, path, classDeclaration) => {
191
+ const baseName = extendedClassNameOf(classDeclaration);
192
+ if (!baseName) {
193
+ return undefined;
194
+ }
195
+
196
+ // Declaration bundles are flattened: the base class often sits in the same file.
197
+ if (findClass(sourceFile, baseName)) {
198
+ return { path, className: baseName };
199
+ }
200
+
201
+ const imported = importOf(sourceFile, baseName);
202
+ if (!imported) {
203
+ return undefined;
204
+ }
205
+
206
+ const target = resolveModule(imported.specifier, path);
207
+ return target ? { path: target, className: imported.exportedName } : undefined;
208
+ };
209
+
210
+ const collectBase = (sourceFile, path, classDeclaration, kinds, visited) => {
211
+ const target = baseTargetOf(sourceFile, path, classDeclaration);
212
+ if (target) {
213
+ collect(target.path, target.className, kinds, visited);
214
+ }
215
+ };
216
+
156
217
  const collect = (path, className, kinds, visited) => {
157
218
  const key = `${path}::${className ?? "*"}`;
158
219
  if (visited.has(key)) {
@@ -165,47 +226,17 @@ const collect = (path, className, kinds, visited) => {
165
226
  return;
166
227
  }
167
228
 
168
- const classDeclarations = className
169
- ? [findClass(sourceFile, className)].filter(Boolean)
170
- : sourceFile.statements.filter(ts.isClassDeclaration);
229
+ const classDeclarations = classDeclarationsOf(sourceFile, className);
171
230
 
172
231
  // A barrel: the class is only re-exported from here.
173
- if (className && classDeclarations.length === 0) {
174
- for (const specifier of reExportedModulesOf(sourceFile)) {
175
- const target = resolveModule(specifier, path);
176
- if (target) {
177
- collect(target, className, kinds, visited);
178
- }
179
- }
232
+ if (isBarrelMiss(className, classDeclarations)) {
233
+ collectFromReExports(sourceFile, path, className, kinds, visited);
180
234
  return;
181
235
  }
182
236
 
183
237
  for (const classDeclaration of classDeclarations) {
184
- for (const member of classDeclaration.members) {
185
- if (!member.name || !ts.isIdentifier(member.name)) {
186
- continue;
187
- }
188
- const kind = kindOfMember(member);
189
- // A subclass overriding a member wins: it is collected first.
190
- if (kind && !kinds.has(member.name.text)) {
191
- kinds.set(member.name.text, kind);
192
- }
193
- }
194
-
195
- const baseName = extendedClassNameOf(classDeclaration);
196
- if (!baseName) {
197
- continue;
198
- }
199
- // Declaration bundles are flattened: the base class often sits in the same file.
200
- if (findClass(sourceFile, baseName)) {
201
- collect(path, baseName, kinds, visited);
202
- continue;
203
- }
204
- const imported = importOf(sourceFile, baseName);
205
- const target = imported && resolveModule(imported.specifier, path);
206
- if (target) {
207
- collect(target, imported.exportedName, kinds, visited);
208
- }
238
+ collectMemberKinds(classDeclaration, kinds);
239
+ collectBase(sourceFile, path, classDeclaration, kinds, visited);
209
240
  }
210
241
  };
211
242