@cplieger/deadset-ts 7.2.0-dev.3 → 8.0.0-dev.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.
Files changed (153) hide show
  1. package/dist/bin/mapper-connection.js +7 -4
  2. package/dist/src/analysis.js +116 -16
  3. package/dist/src/augmentations.js +21 -8
  4. package/dist/src/automatic-types.js +189 -0
  5. package/dist/src/base-url.js +18 -0
  6. package/dist/src/component-files.js +96 -31
  7. package/dist/src/components.js +7 -6
  8. package/dist/src/config.js +1 -0
  9. package/dist/src/configuration-files.js +54 -15
  10. package/dist/src/configured-declarations.js +24 -2
  11. package/dist/src/conformance-record.js +2 -2
  12. package/dist/src/contextual-uses.js +26 -26
  13. package/dist/src/convention-rows.js +75 -0
  14. package/dist/src/conventions.js +101 -5
  15. package/dist/src/decorator.js +21 -18
  16. package/dist/src/dependencies.js +185 -30
  17. package/dist/src/destructuring.js +77 -2
  18. package/dist/src/discover.js +188 -85
  19. package/dist/src/edges.js +1 -1
  20. package/dist/src/entry-point-gaps.js +7 -10
  21. package/dist/src/entry-points.js +112 -11
  22. package/dist/src/findings/completion.js +116 -21
  23. package/dist/src/findings/dependencies-and-module-machinery.js +71 -25
  24. package/dist/src/findings/emitters.js +1 -1
  25. package/dist/src/findings/intra-function.js +44 -13
  26. package/dist/src/findings/non-code-artifacts.js +66 -33
  27. package/dist/src/findings/unused-declarations.js +21 -1
  28. package/dist/src/findings/visibility-narrowing.js +22 -3
  29. package/dist/src/generated-file.js +11 -0
  30. package/dist/src/git-ignore.js +86 -0
  31. package/dist/src/graph.js +4 -1
  32. package/dist/src/import-bindings.js +97 -0
  33. package/dist/src/indexed-access.js +130 -28
  34. package/dist/src/interface-satisfaction.js +30 -12
  35. package/dist/src/intra-function-parts.js +313 -50
  36. package/dist/src/inventory.js +63 -8
  37. package/dist/src/json-document.js +5 -5
  38. package/dist/src/kinds.js +13 -13
  39. package/dist/src/matrix.js +18 -9
  40. package/dist/src/mdx-entries.js +197 -0
  41. package/dist/src/module-declarations.js +9 -13
  42. package/dist/src/namespace-objects.js +28 -18
  43. package/dist/src/overrides.js +45 -9
  44. package/dist/src/paths.js +9 -0
  45. package/dist/src/print.js +1 -0
  46. package/dist/src/ref.js +10 -0
  47. package/dist/src/references.js +29 -13
  48. package/dist/src/reflective-lookup.js +8 -6
  49. package/dist/src/report.js +4 -2
  50. package/dist/src/reporters/template.js +22 -13
  51. package/dist/src/resolve.js +2 -0
  52. package/dist/src/roots.js +110 -38
  53. package/dist/src/schema.js +1 -0
  54. package/dist/src/serialization-contract.js +97 -20
  55. package/dist/src/session.js +26 -1
  56. package/dist/src/source-files.js +39 -14
  57. package/dist/src/spelled-names.js +58 -0
  58. package/dist/src/suppress-file.js +12 -4
  59. package/dist/src/suppress-inline.js +21 -3
  60. package/dist/src/suppress.js +7 -5
  61. package/dist/src/sweep.js +27 -10
  62. package/dist/src/symbol-keys.js +103 -2
  63. package/dist/src/template-field.js +9 -6
  64. package/dist/src/test-support.js +121 -17
  65. package/dist/src/type-errors.js +116 -35
  66. package/dist/src/type-parameter-reads.js +172 -0
  67. package/dist/src/value-reach.js +17 -5
  68. package/dist/src/verbs/analyze.js +19 -1
  69. package/dist/src/verbs/explain.js +4 -2
  70. package/dist/src/version-range.js +10 -5
  71. package/dist/src/version.js +1 -1
  72. package/dist/src/workflow-steps.js +119 -22
  73. package/dist/src/workspace-programs.js +7 -3
  74. package/dist/src/workspace-resolution.js +72 -13
  75. package/dist/src/workspace.js +1 -1
  76. package/jsr.json +1 -1
  77. package/package.json +2 -2
  78. package/src/analysis.ts +175 -16
  79. package/src/augmentations.ts +21 -10
  80. package/src/automatic-types.ts +246 -0
  81. package/src/base-url.ts +24 -0
  82. package/src/component-files.ts +116 -31
  83. package/src/components.ts +7 -6
  84. package/src/config.ts +3 -0
  85. package/src/configuration-files.ts +80 -18
  86. package/src/configured-declarations.ts +33 -4
  87. package/src/conformance-record.ts +2 -2
  88. package/src/contextual-uses.ts +27 -27
  89. package/src/convention-rows.ts +103 -0
  90. package/src/conventions.ts +121 -7
  91. package/src/decorator.ts +28 -18
  92. package/src/dependencies.ts +249 -43
  93. package/src/destructuring.ts +93 -2
  94. package/src/discover.ts +234 -86
  95. package/src/edges.ts +1 -1
  96. package/src/entry-point-gaps.ts +8 -11
  97. package/src/entry-points.ts +133 -11
  98. package/src/finding.ts +10 -0
  99. package/src/findings/completion.ts +147 -24
  100. package/src/findings/dependencies-and-module-machinery.ts +90 -28
  101. package/src/findings/emitters.ts +1 -1
  102. package/src/findings/intra-function.ts +56 -14
  103. package/src/findings/non-code-artifacts.ts +81 -41
  104. package/src/findings/unused-declarations.ts +25 -1
  105. package/src/findings/visibility-narrowing.ts +32 -3
  106. package/src/generated-file.ts +16 -0
  107. package/src/git-ignore.ts +98 -0
  108. package/src/graph.ts +6 -0
  109. package/src/host.ts +5 -9
  110. package/src/import-bindings.ts +128 -0
  111. package/src/indexed-access.ts +152 -30
  112. package/src/interface-satisfaction.ts +33 -13
  113. package/src/intra-function-parts.ts +415 -64
  114. package/src/inventory.ts +83 -7
  115. package/src/json-document.ts +7 -7
  116. package/src/kinds.ts +13 -13
  117. package/src/matrix.ts +32 -14
  118. package/src/mdx-entries.ts +241 -0
  119. package/src/module-declarations.ts +11 -13
  120. package/src/namespace-objects.ts +34 -21
  121. package/src/overrides.ts +56 -9
  122. package/src/paths.ts +10 -0
  123. package/src/print.ts +1 -0
  124. package/src/ref.ts +12 -0
  125. package/src/references.ts +38 -17
  126. package/src/reflective-lookup.ts +9 -7
  127. package/src/report.ts +16 -3
  128. package/src/reporters/template.ts +21 -13
  129. package/src/resolve.ts +3 -0
  130. package/src/roots.ts +163 -45
  131. package/src/schema.ts +1 -0
  132. package/src/serialization-contract.ts +108 -17
  133. package/src/session.ts +29 -1
  134. package/src/source-files.ts +66 -16
  135. package/src/spelled-names.ts +102 -0
  136. package/src/suppress-file.ts +14 -3
  137. package/src/suppress-inline.ts +21 -2
  138. package/src/suppress.ts +9 -5
  139. package/src/sweep.ts +32 -11
  140. package/src/symbol-keys.ts +129 -2
  141. package/src/template-field.ts +9 -6
  142. package/src/test-support.ts +149 -16
  143. package/src/type-errors.ts +132 -41
  144. package/src/type-parameter-reads.ts +227 -0
  145. package/src/value-reach.ts +19 -6
  146. package/src/verbs/analyze.ts +28 -1
  147. package/src/verbs/explain.ts +5 -2
  148. package/src/version-range.ts +10 -5
  149. package/src/version.ts +1 -1
  150. package/src/workflow-steps.ts +127 -22
  151. package/src/workspace-programs.ts +7 -3
  152. package/src/workspace-resolution.ts +86 -14
  153. package/src/workspace.ts +1 -1
@@ -31,6 +31,22 @@ export interface ShortNameReading extends MoveReading {
31
31
  readonly package: string;
32
32
  }
33
33
 
34
+ /**
35
+ * One configuration property whose values name packages by the tool's prefix convention:
36
+ * `x` stands for `<prefix>-x`, `@s` for `@s/<prefix>` and `@s/x` for `@s/<prefix>-x`, and
37
+ * a name already holding the prefix for itself.
38
+ */
39
+ export interface PrefixedNameReading extends MoveReading {
40
+ /** The prefix the convention puts before a short name. */
41
+ readonly prefix: string;
42
+ /**
43
+ * The text a value starts with to name a package, the name ending at its last `/`.
44
+ * Absent, every value without a `:` names one, the name ending at its first `/` past
45
+ * a scope.
46
+ */
47
+ readonly marker?: string;
48
+ }
49
+
34
50
  /**
35
51
  * One configuration property whose value makes the tool's output import a runtime helper
36
52
  * package, so the package is used where the property holds that value.
@@ -63,6 +79,14 @@ export interface TestFileReading extends MoveReading {
63
79
  readonly roots: readonly string[];
64
80
  }
65
81
 
82
+ /** One member of the declaring `package.json` whose strings name the tool's entries. */
83
+ export interface ManifestEntryKey {
84
+ /** The member's path through nested objects, its keys joined by `.`, a `*` key standing for every member. */
85
+ readonly key: string;
86
+ /** Whether each string names a directory of entries, or one entry, its extension optional. */
87
+ readonly directory: boolean;
88
+ }
89
+
66
90
  /** One directory a row's globs name that a configuration property moves. */
67
91
  export interface DirectoryMove {
68
92
  /** The placeholder the row's globs and later moves write as `<id>`. */
@@ -96,6 +120,8 @@ export interface ConventionRow {
96
120
  readonly moves: readonly DirectoryMove[];
97
121
  /** The properties that name a package the configuration uses by a short name. */
98
122
  readonly shortNames?: readonly ShortNameReading[];
123
+ /** The properties that name a package the configuration uses by its prefix convention. */
124
+ readonly prefixedNames?: readonly PrefixedNameReading[];
99
125
  /**
100
126
  * The properties whose value only selects files to test, mutate, cover or ignore, so a
101
127
  * string there names no file the tool runs or loads.
@@ -110,6 +136,8 @@ export interface ConventionRow {
110
136
  readonly packageKeys?: readonly PackageKey[];
111
137
  /** The members of a `package.json` the tool reads its configuration from. */
112
138
  readonly manifestKeys?: readonly string[];
139
+ /** The members of the declaring `package.json` whose strings name entries, as an entry glob does. */
140
+ readonly manifestEntries?: readonly ManifestEntryKey[];
113
141
  /** The settings that make the tool's output import a runtime helper package. */
114
142
  readonly helpers?: readonly HelperReading[];
115
143
  }
@@ -118,6 +146,8 @@ const SCRIPT = "{js,jsx,ts,tsx}";
118
146
  const MODULE = "{js,mjs,ts,mts}";
119
147
  const ANY_MODULE = "{js,mjs,cjs,ts,mts,cts}";
120
148
  const VITE_CONFIG = `vite.config.${ANY_MODULE}`;
149
+ /** The legacy configuration files of ESLint that hold JSON or a CommonJS module. */
150
+ const ESLINTRC = [".eslintrc", ".eslintrc.json", ".eslintrc.{js,cjs}"];
121
151
 
122
152
  const SVELTEKIT_ROUTE_FILES = "+{page,layout,server,page.server,layout.server,error}";
123
153
 
@@ -231,6 +261,25 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
231
261
  package: "@babel/runtime",
232
262
  })),
233
263
  },
264
+ {
265
+ name: "docusaurus",
266
+ package: "@docusaurus/core",
267
+ range: ">=2.0.0 <4.0.0",
268
+ entries: [`src/pages/**/*.${SCRIPT}`, `src/theme/**/*.${SCRIPT}`],
269
+ moves: [],
270
+ },
271
+ {
272
+ name: "eslint",
273
+ package: "eslint",
274
+ range: ">=4.0.0 <10.0.0",
275
+ entries: [],
276
+ moves: [],
277
+ prefixedNames: [
278
+ { property: "plugins", files: ESLINTRC, prefix: "eslint-plugin" },
279
+ { property: "extends", files: ESLINTRC, prefix: "eslint-plugin", marker: "plugin:" },
280
+ { property: "extends", files: ESLINTRC, prefix: "eslint-config" },
281
+ ],
282
+ },
234
283
  {
235
284
  name: "eslint",
236
285
  package: "eslint",
@@ -238,6 +287,13 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
238
287
  entries: [`**/eslint.config.${ANY_MODULE}`],
239
288
  moves: [],
240
289
  },
290
+ {
291
+ name: "expo",
292
+ package: "expo",
293
+ range: ">=44.0.0 <60.0.0",
294
+ entries: [`{index,App}.${SCRIPT}`],
295
+ moves: [],
296
+ },
241
297
  {
242
298
  name: "expo-router",
243
299
  package: "expo-router",
@@ -322,6 +378,28 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
322
378
  },
323
379
  ],
324
380
  },
381
+ {
382
+ name: "oclif",
383
+ package: "@oclif/core",
384
+ range: ">=1.0.0 <5.0.0",
385
+ entries: [`src/{commands,hooks}/**/*.${SCRIPT}`],
386
+ moves: [],
387
+ manifestEntries: [
388
+ { key: "oclif.commands", directory: true },
389
+ { key: "oclif.hooks", directory: false },
390
+ ],
391
+ },
392
+ {
393
+ name: "parcel",
394
+ package: "parcel",
395
+ range: ">=2.0.0 <3.0.0",
396
+ entries: [],
397
+ moves: [],
398
+ manifestEntries: [
399
+ { key: "source", directory: false },
400
+ { key: "targets.*.source", directory: false },
401
+ ],
402
+ },
325
403
  {
326
404
  name: "prettier",
327
405
  package: "prettier",
@@ -613,6 +691,31 @@ export const CONVENTION_ROWS: readonly ConventionRow[] = [
613
691
  },
614
692
  ],
615
693
  },
694
+ {
695
+ name: "tanstack-router",
696
+ package: "@tanstack/router-plugin",
697
+ range: ">=1.0.0 <2.0.0",
698
+ entries: [`<routes>/**/*.${SCRIPT}`],
699
+ moves: [
700
+ {
701
+ id: "routes",
702
+ defaults: ["src/routes"],
703
+ readings: [
704
+ { property: "routesDirectory", files: ["tsr.config.json"] },
705
+ {
706
+ property: "routesDirectory",
707
+ files: [VITE_CONFIG],
708
+ call: { module: "@tanstack/router-plugin/vite", export: "tanstackRouter" },
709
+ },
710
+ {
711
+ property: "routesDirectory",
712
+ files: [VITE_CONFIG],
713
+ call: { module: "@tanstack/router-plugin/vite", export: "TanStackRouterVite" },
714
+ },
715
+ ],
716
+ },
717
+ ],
718
+ },
616
719
  {
617
720
  name: "vite",
618
721
  package: "vite",
@@ -51,9 +51,11 @@ import {
51
51
  type ConventionRow,
52
52
  type DirectoryMove,
53
53
  type HelperReading,
54
+ type ManifestEntryKey,
54
55
  type MoveReading,
55
56
  type OptionsCall,
56
57
  type PackageKey,
58
+ type PrefixedNameReading,
57
59
  type ShortNameReading,
58
60
  type TestFileReading,
59
61
  } from "./convention-rows.ts";
@@ -692,6 +694,47 @@ function manifestKeyUses(manifest: unknown, keys: readonly string[]): readonly s
692
694
  );
693
695
  }
694
696
 
697
+ /** The extensions an entry a manifest entry key names without one may carry. */
698
+ const ENTRY_EXTENSIONS = "{js,jsx,mjs,cjs,ts,tsx,mts,cts}";
699
+
700
+ /** The characters that make a manifest string a pattern rather than a directory's name. */
701
+ const PATTERN = /[*?{}[\]]/u;
702
+
703
+ /**
704
+ * The entry globs one applied row's manifest entry keys write, below the manifest's
705
+ * directory: a directory's every module, or a file with or without an extension. A string
706
+ * that leaves the directory names nothing.
707
+ */
708
+ function manifestEntryGlobs(manifest: unknown, keys: readonly ManifestEntryKey[]): string[] {
709
+ const at = (value: unknown, path: readonly string[]): unknown[] => {
710
+ const [key, ...rest] = path;
711
+ if (key === undefined) {
712
+ return [value];
713
+ }
714
+ if (!isRecord(value)) {
715
+ return [];
716
+ }
717
+ const next = key === "*" ? Object.values(value) : Object.hasOwn(value, key) ? [value[key]] : [];
718
+ return next.flatMap((one) => at(one, rest));
719
+ };
720
+ return keys.flatMap(({ key, directory }) =>
721
+ at(manifest, key.split(".")).flatMap((value) =>
722
+ documentStrings(value).flatMap((text) => {
723
+ const path = normalizePath(text)
724
+ .replace(/^(?:\.\/)+/u, "")
725
+ .replace(/\/+$/u, "");
726
+ if (path === "" || path === ".." || path.startsWith("../") || path.startsWith("/")) {
727
+ return [];
728
+ }
729
+ if (!directory) {
730
+ return [path, `${path}.${ENTRY_EXTENSIONS}`];
731
+ }
732
+ return PATTERN.test(path) ? [] : [`${path}/**/*.${ENTRY_EXTENSIONS}`];
733
+ }),
734
+ ),
735
+ );
736
+ }
737
+
695
738
  /** What one configuration file states about one property. */
696
739
  interface PropertyReading {
697
740
  /** The literal values the file sets the property to. */
@@ -892,6 +935,66 @@ function shortNameUses(
892
935
  return [...used].filter((name) => declared.has(name)).sort(compare);
893
936
  }
894
937
 
938
+ /**
939
+ * The package one value of a prefixed-name reading names, or none: ESLint's own naming,
940
+ * https://github.com/eslint/eslintrc/blob/main/lib/shared/naming.js
941
+ */
942
+ function prefixedPackage(value: string, reading: PrefixedNameReading): string | undefined {
943
+ const { prefix, marker } = reading;
944
+ let name: string;
945
+ if (marker === undefined) {
946
+ if (value.includes(":") || value.startsWith(".") || value.startsWith("/")) {
947
+ return undefined;
948
+ }
949
+ const parts = value.split("/");
950
+ name = (value.startsWith("@") ? parts.slice(0, 2) : parts.slice(0, 1)).join("/");
951
+ } else {
952
+ if (!value.startsWith(marker) || !value.includes("/", marker.length)) {
953
+ return undefined;
954
+ }
955
+ name = value.slice(marker.length, value.lastIndexOf("/"));
956
+ }
957
+ if (name === "") {
958
+ return undefined;
959
+ }
960
+ if (!name.startsWith("@")) {
961
+ return name.startsWith(`${prefix}-`) ? name : `${prefix}-${name}`;
962
+ }
963
+ const [scope, rest] = name.split("/");
964
+ if (rest === undefined || rest === "" || rest === prefix) {
965
+ return `${scope ?? name}/${prefix}`;
966
+ }
967
+ return rest.startsWith(`${prefix}-`) ? name : `${scope ?? name}/${prefix}-${rest}`;
968
+ }
969
+
970
+ /** The declared dependencies an applied row's prefixed names use. */
971
+ function prefixedNameUses(
972
+ host: Host,
973
+ parse: ParseSource,
974
+ dir: string,
975
+ manifest: unknown,
976
+ readings: readonly PrefixedNameReading[],
977
+ ): readonly string[] {
978
+ const declared = new Set(
979
+ DEPENDENCY_SECTIONS.flatMap((section) => {
980
+ const held = isRecord(manifest) ? manifest[section] : undefined;
981
+ return isRecord(held) ? Object.keys(held) : [];
982
+ }),
983
+ );
984
+ const used = new Set<string>();
985
+ for (const reading of readings) {
986
+ for (const { file } of readingFiles(host, parse, dir, reading.files)) {
987
+ for (const value of readProperty(file, reading.property, reading.call, true).values) {
988
+ const named = prefixedPackage(value, reading);
989
+ if (named !== undefined && declared.has(named)) {
990
+ used.add(named);
991
+ }
992
+ }
993
+ }
994
+ }
995
+ return [...used].sort(compare);
996
+ }
997
+
895
998
  /** The declared dependencies an applied row's helper settings use. */
896
999
  function helperUses(
897
1000
  host: Host,
@@ -1064,6 +1167,10 @@ export function readConventions(
1064
1167
  for (const name of shortNameUses(host, parse, manifest.dir, declared, row.shortNames ?? [])) {
1065
1168
  uses.add(name);
1066
1169
  }
1170
+ const prefixed = row.prefixedNames ?? [];
1171
+ for (const name of prefixedNameUses(host, parse, manifest.dir, declared, prefixed)) {
1172
+ uses.add(name);
1173
+ }
1067
1174
  const keys = row.packageKeys ?? [];
1068
1175
  for (const name of packageKeyUses(host, parse, manifest.dir, declared, keys)) {
1069
1176
  uses.add(name);
@@ -1082,7 +1189,11 @@ export function readConventions(
1082
1189
  for (const below of new Set((row.generated ?? []).flatMap((one) => expand(one, resolved)))) {
1083
1190
  generated.push({ dir: manifest.dir, row: row.name, below });
1084
1191
  }
1085
- for (const glob of new Set(row.entries.flatMap((entry) => expand(entry, resolved)))) {
1192
+ const written = [
1193
+ ...row.entries.flatMap((entry) => expand(entry, resolved)),
1194
+ ...manifestEntryGlobs(declared, row.manifestEntries ?? []),
1195
+ ];
1196
+ for (const glob of new Set(written)) {
1086
1197
  globs.push({
1087
1198
  dir: manifest.dir,
1088
1199
  row: row.name,
@@ -1138,12 +1249,15 @@ export function conventionEntries(
1138
1249
  const found = new Map<string, ConventionEntry>();
1139
1250
  for (const glob of conventions.globs) {
1140
1251
  for (const file of files.byName.values()) {
1141
- const path = relativePath(glob.dir, file.fileName);
1142
- if (
1143
- path !== undefined &&
1144
- glob.expression.test(path) &&
1145
- !glob.excludes.some((one) => one.test(path))
1146
- ) {
1252
+ const matched = [file.fileName, ...files.writtenAs(file)].some((name) => {
1253
+ const path = relativePath(glob.dir, name);
1254
+ return (
1255
+ path !== undefined &&
1256
+ glob.expression.test(path) &&
1257
+ !glob.excludes.some((one) => one.test(path))
1258
+ );
1259
+ });
1260
+ if (matched) {
1147
1261
  found.set(`${file.fileName}\u0000${glob.row}`, { file, row: glob.row });
1148
1262
  }
1149
1263
  }
package/src/decorator.ts CHANGED
@@ -1,14 +1,10 @@
1
1
  /**
2
- * The decorator class: a member a decorator expression is attached to, and every
3
- * member of a class a decorator expression is attached to, is held back, because the
4
- * decorator receives the member or the class when the class is defined and may reach
5
- * the member by its name.
6
- *
7
- * The rule is read from the tree alone: a decorator is attached where it is written.
8
- * The use the decorator itself makes of what it decorates is the reference pass's,
9
- * which records it as a `decorator` reference; this class records only that the
10
- * declaration is held back, and the member set of a decorated class is every member
11
- * the inventory holds for the class, static members included.
2
+ * The decorator class: a member a decorator expression is attached to, and every member
3
+ * of a class one is attached to but the member a parameter property declares, is held
4
+ * back, because the decorator may reach the member by its name. The rule is read from
5
+ * the tree alone. The decorator's own use of what it decorates is the reference pass's
6
+ * `decorator` reference; this class records only that the declaration is held back,
7
+ * static members of a decorated class included.
12
8
  */
13
9
 
14
10
  import type { Node } from "@typescript/native/ast";
@@ -44,25 +40,39 @@ export function decorator<Brand>(input: DetectorInput<Brand>): readonly Evidence
44
40
  }
45
41
  }
46
42
 
47
- const found: Evidence[] = [];
43
+ const decorated: {
44
+ readonly symbol: InventorySymbol;
45
+ readonly detail: string;
46
+ readonly site: Evidence["site"];
47
+ }[] = [];
48
+ // A decorated class's container passes each constructor argument, which keeps the
49
+ // parameter, not the member a parameter property stores it in.
50
+ const parameterMembers = new Set<string>();
48
51
  for (const file of project.ownSourceFiles()) {
49
52
  const visit = (node: Node): void => {
50
53
  const id = held.declarations.get(nodeKey(file, node));
51
54
  const symbol = id === undefined ? undefined : byId.get(id);
55
+ if (id !== undefined && isParameterProperty(node)) {
56
+ parameterMembers.add(id);
57
+ }
52
58
  // A parameter property's decorator is attached to its parameter, and to no member.
53
59
  if (symbol !== undefined && DECORATED.has(symbol.kind) && !isParameterProperty(node)) {
54
60
  for (const attached of decoratorsOf(node)) {
55
- const detail = `decorated by ${spelledDecorator(attached)}`;
56
- const site = renderPosition(file, targetRoot, attached.getStart());
57
- const reached = symbol.kind === "class" ? (membersOf.get(symbol.id) ?? []) : [symbol];
58
- for (const member of reached) {
59
- found.push({ id: member.id, detail, site });
60
- }
61
+ decorated.push({
62
+ symbol,
63
+ detail: `decorated by ${spelledDecorator(attached)}`,
64
+ site: renderPosition(file, targetRoot, attached.getStart()),
65
+ });
61
66
  }
62
67
  }
63
68
  node.forEachChild(visit);
64
69
  };
65
70
  file.forEachChild(visit);
66
71
  }
67
- return found;
72
+ return decorated.flatMap(({ symbol, detail, site }) =>
73
+ (symbol.kind === "class"
74
+ ? (membersOf.get(symbol.id) ?? []).filter((member) => !parameterMembers.has(member.id))
75
+ : [symbol]
76
+ ).map((member) => ({ id: member.id, detail, site })),
77
+ );
68
78
  }