@craft-ts/dev-tools 0.7.0-beta.13

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 (237) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +154 -0
  3. package/generators.json +26 -0
  4. package/package.json +119 -0
  5. package/src/bin/craft-architecture-check.d.ts +2 -0
  6. package/src/bin/craft-architecture-check.js +51 -0
  7. package/src/bin/craft-architecture-check.js.map +1 -0
  8. package/src/bin/craft-brand.d.ts +2 -0
  9. package/src/bin/craft-brand.js +9 -0
  10. package/src/bin/craft-brand.js.map +1 -0
  11. package/src/bin/craft-graph.d.ts +2 -0
  12. package/src/bin/craft-graph.js +72 -0
  13. package/src/bin/craft-graph.js.map +1 -0
  14. package/src/bin/craft-migrate-architecture.d.ts +2 -0
  15. package/src/bin/craft-migrate-architecture.js +64 -0
  16. package/src/bin/craft-migrate-architecture.js.map +1 -0
  17. package/src/bin/craft-migrate-components.d.ts +2 -0
  18. package/src/bin/craft-migrate-components.js +67 -0
  19. package/src/bin/craft-migrate-components.js.map +1 -0
  20. package/src/bin/craft-migrate-primitives.d.ts +2 -0
  21. package/src/bin/craft-migrate-primitives.js +71 -0
  22. package/src/bin/craft-migrate-primitives.js.map +1 -0
  23. package/src/bin/craft-migrate-routes.d.ts +2 -0
  24. package/src/bin/craft-migrate-routes.js +77 -0
  25. package/src/bin/craft-migrate-routes.js.map +1 -0
  26. package/src/bin/craft-migrate-services.d.ts +2 -0
  27. package/src/bin/craft-migrate-services.js +75 -0
  28. package/src/bin/craft-migrate-services.js.map +1 -0
  29. package/src/bin/craft-migrate-template.d.ts +2 -0
  30. package/src/bin/craft-migrate-template.js +68 -0
  31. package/src/bin/craft-migrate-template.js.map +1 -0
  32. package/src/bin/craft-migrate.d.ts +2 -0
  33. package/src/bin/craft-migrate.js +91 -0
  34. package/src/bin/craft-migrate.js.map +1 -0
  35. package/src/bin/craft.d.ts +2 -0
  36. package/src/bin/craft.js +318 -0
  37. package/src/bin/craft.js.map +1 -0
  38. package/src/eslint-rules/anchor-has-href.cjs +35 -0
  39. package/src/eslint-rules/app-start-registry-match.cjs +355 -0
  40. package/src/eslint-rules/button-has-type.cjs +50 -0
  41. package/src/eslint-rules/control-has-accessible-name.cjs +56 -0
  42. package/src/eslint-rules/craft-component-name-match.cjs +8 -0
  43. package/src/eslint-rules/craft-computed-name-match.cjs +63 -0
  44. package/src/eslint-rules/craft-css-token-registry.cjs +17 -0
  45. package/src/eslint-rules/craft-css-var-naming.cjs +18 -0
  46. package/src/eslint-rules/craft-css-vars-contract.cjs +34 -0
  47. package/src/eslint-rules/craft-directive-name-match.cjs +8 -0
  48. package/src/eslint-rules/craft-method-name-match.cjs +8 -0
  49. package/src/eslint-rules/craft-name-match-utils.cjs +179 -0
  50. package/src/eslint-rules/craft-primitive-context.cjs +46 -0
  51. package/src/eslint-rules/craft-signal-source-name-match.cjs +8 -0
  52. package/src/eslint-rules/craft-source-name-match.cjs +8 -0
  53. package/src/eslint-rules/craft-styles-scope-safe.cjs +25 -0
  54. package/src/eslint-rules/css-rule-utils.cjs +109 -0
  55. package/src/eslint-rules/global-exception-registry-match.cjs +562 -0
  56. package/src/eslint-rules/heading-has-content.cjs +31 -0
  57. package/src/eslint-rules/html-helpers.cjs +115 -0
  58. package/src/eslint-rules/hyperscript-walk.cjs +249 -0
  59. package/src/eslint-rules/iframe-has-title.cjs +26 -0
  60. package/src/eslint-rules/img-has-alt.cjs +28 -0
  61. package/src/eslint-rules/index.cjs +209 -0
  62. package/src/eslint-rules/index.d.cts +5 -0
  63. package/src/eslint-rules/label-has-associated-control.cjs +67 -0
  64. package/src/eslint-rules/no-async-await.cjs +46 -0
  65. package/src/eslint-rules/no-craft-computed-side-effects.cjs +301 -0
  66. package/src/eslint-rules/no-craft-use-in-template.cjs +89 -0
  67. package/src/eslint-rules/no-craft-use.cjs +42 -0
  68. package/src/eslint-rules/no-direct-temporal-globals.cjs +73 -0
  69. package/src/eslint-rules/no-effect-in-params.cjs +265 -0
  70. package/src/eslint-rules/no-effect-outside-loaders.cjs +3 -0
  71. package/src/eslint-rules/no-ephemeral-template-form-state.cjs +131 -0
  72. package/src/eslint-rules/no-hardcoded-design-values.cjs +15 -0
  73. package/src/eslint-rules/no-heading-level-skip.cjs +101 -0
  74. package/src/eslint-rules/no-imperative-craft-resource-trigger.cjs +291 -0
  75. package/src/eslint-rules/no-important-in-component-styles.cjs +12 -0
  76. package/src/eslint-rules/no-injection-token.cjs +173 -0
  77. package/src/eslint-rules/no-invalid-insertion-pipe.cjs +162 -0
  78. package/src/eslint-rules/no-manual-route-provider-list.cjs +85 -0
  79. package/src/eslint-rules/no-noninteractive-element-interactions.cjs +66 -0
  80. package/src/eslint-rules/no-positive-tabindex.cjs +44 -0
  81. package/src/eslint-rules/no-redundant-primitive-insertion.cjs +361 -0
  82. package/src/eslint-rules/no-render-writes.cjs +106 -0
  83. package/src/eslint-rules/no-throw.cjs +132 -0
  84. package/src/eslint-rules/no-transition-actions.cjs +133 -0
  85. package/src/eslint-rules/no-type-assertions-in-template.cjs +211 -0
  86. package/src/eslint-rules/no-widened-route-provider-context.cjs +39 -0
  87. package/src/eslint-rules/prefer-browser-boundaries.cjs +92 -0
  88. package/src/eslint-rules/prefer-craft-http-transport.cjs +103 -0
  89. package/src/eslint-rules/prefer-craft-template-blocks.cjs +492 -0
  90. package/src/eslint-rules/prefer-direct-yieldable-callback.cjs +105 -0
  91. package/src/eslint-rules/prefer-named-html-helpers.cjs +33 -0
  92. package/src/eslint-rules/prefer-query-method-over-state-trigger.cjs +282 -0
  93. package/src/eslint-rules/prefer-relative-heading.cjs +43 -0
  94. package/src/eslint-rules/provide-host-name-match-component.cjs +455 -0
  95. package/src/eslint-rules/recommended-config.cjs +77 -0
  96. package/src/eslint-rules/require-assert-exhaustive-route-exceptions.cjs +259 -0
  97. package/src/eslint-rules/require-cascade-route-di-check.cjs +223 -0
  98. package/src/eslint-rules/require-child-route-mount-check.cjs +224 -0
  99. package/src/eslint-rules/require-component-monitoring.cjs +212 -0
  100. package/src/eslint-rules/require-craft-exception-handler.cjs +142 -0
  101. package/src/eslint-rules/require-craft-method-for-yieldable-callback.cjs +236 -0
  102. package/src/eslint-rules/require-craft-resource-trigger-yield.cjs +209 -0
  103. package/src/eslint-rules/require-effect-adapters.cjs +86 -0
  104. package/src/eslint-rules/require-exception-component-di-check.cjs +314 -0
  105. package/src/eslint-rules/require-focus-visible.cjs +36 -0
  106. package/src/eslint-rules/require-interactive-local-name.cjs +105 -0
  107. package/src/eslint-rules/require-lazy-load-with-retry.cjs +148 -0
  108. package/src/eslint-rules/require-outlet-heading-section.cjs +70 -0
  109. package/src/eslint-rules/require-pending-component-di-check.cjs +490 -0
  110. package/src/eslint-rules/require-primitive-context.cjs +156 -0
  111. package/src/eslint-rules/require-primitive-derived-property.cjs +598 -0
  112. package/src/eslint-rules/require-primitive-generator-unwrap.cjs +274 -0
  113. package/src/eslint-rules/require-reactive-template-bindings.cjs +305 -0
  114. package/src/eslint-rules/require-reduced-motion.cjs +38 -0
  115. package/src/eslint-rules/require-route-heading-outline.cjs +166 -0
  116. package/src/eslint-rules/require-yieldable-insertion-write.cjs +98 -0
  117. package/src/eslint-rules/require-yieldable-reactive-read.cjs +102 -0
  118. package/src/eslint-rules/require-yieldable-template-method.cjs +424 -0
  119. package/src/eslint-rules/role-has-required-aria.cjs +38 -0
  120. package/src/eslint-rules/server-function-client-match.cjs +283 -0
  121. package/src/eslint-rules/target-blank-noopener.cjs +31 -0
  122. package/src/eslint-rules/template-element-name-unique.cjs +124 -0
  123. package/src/eslint-rules/valid-aria.cjs +38 -0
  124. package/src/generators/route/compat.d.ts +3 -0
  125. package/src/generators/route/compat.js +6 -0
  126. package/src/generators/route/compat.js.map +1 -0
  127. package/src/generators/route/generator.d.ts +31 -0
  128. package/src/generators/route/generator.js +402 -0
  129. package/src/generators/route/generator.js.map +1 -0
  130. package/src/generators/route/schema.json +50 -0
  131. package/src/generators/route/split-schema.json +19 -0
  132. package/src/index.d.ts +21 -0
  133. package/src/index.js +23 -0
  134. package/src/index.js.map +1 -0
  135. package/src/scripts/angular-brand-codemod.d.ts +139 -0
  136. package/src/scripts/angular-brand-codemod.js +2318 -0
  137. package/src/scripts/angular-brand-codemod.js.map +1 -0
  138. package/src/scripts/angular-brand-codemod.ts +3985 -0
  139. package/src/scripts/architecture/migrate-architecture.d.ts +14 -0
  140. package/src/scripts/architecture/migrate-architecture.js +534 -0
  141. package/src/scripts/architecture/migrate-architecture.js.map +1 -0
  142. package/src/scripts/architecture/migrate-architecture.ts +653 -0
  143. package/src/scripts/architecture-graph.d.ts +368 -0
  144. package/src/scripts/architecture-graph.js +2191 -0
  145. package/src/scripts/architecture-graph.js.map +1 -0
  146. package/src/scripts/architecture-graph.ts +3136 -0
  147. package/src/scripts/components/migrate-components.d.ts +20 -0
  148. package/src/scripts/components/migrate-components.js +163 -0
  149. package/src/scripts/components/migrate-components.js.map +1 -0
  150. package/src/scripts/components/migrate-components.ts +227 -0
  151. package/src/scripts/components/migration-diagnostic.d.ts +7 -0
  152. package/src/scripts/components/migration-diagnostic.js +2 -0
  153. package/src/scripts/components/migration-diagnostic.js.map +1 -0
  154. package/src/scripts/components/migration-diagnostic.ts +8 -0
  155. package/src/scripts/create/create-project.d.ts +20 -0
  156. package/src/scripts/create/create-project.js +783 -0
  157. package/src/scripts/create/create-project.js.map +1 -0
  158. package/src/scripts/create/create-project.ts +849 -0
  159. package/src/scripts/data-flow-graph.d.ts +36 -0
  160. package/src/scripts/data-flow-graph.js +186 -0
  161. package/src/scripts/data-flow-graph.js.map +1 -0
  162. package/src/scripts/data-flow-graph.ts +240 -0
  163. package/src/scripts/dependency-graph.d.ts +152 -0
  164. package/src/scripts/dependency-graph.js +4456 -0
  165. package/src/scripts/dependency-graph.js.map +1 -0
  166. package/src/scripts/dependency-graph.ts +5908 -0
  167. package/src/scripts/effect-dependency-graph.d.ts +89 -0
  168. package/src/scripts/effect-dependency-graph.js +487 -0
  169. package/src/scripts/effect-dependency-graph.js.map +1 -0
  170. package/src/scripts/effect-dependency-graph.ts +630 -0
  171. package/src/scripts/migrate.d.ts +37 -0
  172. package/src/scripts/migrate.js +98 -0
  173. package/src/scripts/migrate.js.map +1 -0
  174. package/src/scripts/migrate.ts +162 -0
  175. package/src/scripts/migration-workspace.d.ts +2 -0
  176. package/src/scripts/migration-workspace.js +67 -0
  177. package/src/scripts/migration-workspace.js.map +1 -0
  178. package/src/scripts/migration-workspace.ts +93 -0
  179. package/src/scripts/primitives/migrate-named-primitives.d.ts +50 -0
  180. package/src/scripts/primitives/migrate-named-primitives.js +487 -0
  181. package/src/scripts/primitives/migrate-named-primitives.js.map +1 -0
  182. package/src/scripts/primitives/migrate-named-primitives.ts +627 -0
  183. package/src/scripts/primitives/migrate-primitive-generators.d.ts +30 -0
  184. package/src/scripts/primitives/migrate-primitive-generators.js +309 -0
  185. package/src/scripts/primitives/migrate-primitive-generators.js.map +1 -0
  186. package/src/scripts/primitives/migrate-primitive-generators.ts +410 -0
  187. package/src/scripts/primitives/migrate-primitives.d.ts +27 -0
  188. package/src/scripts/primitives/migrate-primitives.js +427 -0
  189. package/src/scripts/primitives/migrate-primitives.js.map +1 -0
  190. package/src/scripts/primitives/migrate-primitives.ts +655 -0
  191. package/src/scripts/primitives/migrate-yieldable-reactive-reads.d.ts +23 -0
  192. package/src/scripts/primitives/migrate-yieldable-reactive-reads.js +382 -0
  193. package/src/scripts/primitives/migrate-yieldable-reactive-reads.js.map +1 -0
  194. package/src/scripts/primitives/migrate-yieldable-reactive-reads.ts +499 -0
  195. package/src/scripts/primitives/migration-diagnostic.d.ts +8 -0
  196. package/src/scripts/primitives/migration-diagnostic.js +2 -0
  197. package/src/scripts/primitives/migration-diagnostic.js.map +1 -0
  198. package/src/scripts/primitives/migration-diagnostic.ts +15 -0
  199. package/src/scripts/routes/migrate-routes.d.ts +34 -0
  200. package/src/scripts/routes/migrate-routes.js +657 -0
  201. package/src/scripts/routes/migrate-routes.js.map +1 -0
  202. package/src/scripts/routes/migrate-routes.ts +895 -0
  203. package/src/scripts/routes/migration-diagnostic.d.ts +16 -0
  204. package/src/scripts/routes/migration-diagnostic.js +2 -0
  205. package/src/scripts/routes/migration-diagnostic.js.map +1 -0
  206. package/src/scripts/routes/migration-diagnostic.ts +25 -0
  207. package/src/scripts/routes/route-command.d.ts +75 -0
  208. package/src/scripts/routes/route-command.js +1169 -0
  209. package/src/scripts/routes/route-command.js.map +1 -0
  210. package/src/scripts/routes/route-command.ts +1807 -0
  211. package/src/scripts/routes/verify-routes.d.ts +60 -0
  212. package/src/scripts/routes/verify-routes.js +1121 -0
  213. package/src/scripts/routes/verify-routes.js.map +1 -0
  214. package/src/scripts/routes/verify-routes.ts +1511 -0
  215. package/src/scripts/services/config.d.ts +2 -0
  216. package/src/scripts/services/config.js +38 -0
  217. package/src/scripts/services/config.js.map +1 -0
  218. package/src/scripts/services/config.ts +60 -0
  219. package/src/scripts/services/migrate-services.d.ts +29 -0
  220. package/src/scripts/services/migrate-services.js +924 -0
  221. package/src/scripts/services/migrate-services.js.map +1 -0
  222. package/src/scripts/services/migrate-services.ts +1348 -0
  223. package/src/scripts/services/migration-diagnostic.d.ts +8 -0
  224. package/src/scripts/services/migration-diagnostic.js +2 -0
  225. package/src/scripts/services/migration-diagnostic.js.map +1 -0
  226. package/src/scripts/services/migration-diagnostic.ts +28 -0
  227. package/src/scripts/template/migrate-template.d.ts +16 -0
  228. package/src/scripts/template/migrate-template.js +349 -0
  229. package/src/scripts/template/migrate-template.js.map +1 -0
  230. package/src/scripts/template/migrate-template.ts +418 -0
  231. package/src/template-migration.d.ts +1 -0
  232. package/src/template-migration.js +2 -0
  233. package/src/template-migration.js.map +1 -0
  234. package/src/testing.d.ts +19 -0
  235. package/src/testing.js +2 -0
  236. package/src/testing.js.map +1 -0
  237. package/src/tsconfig.codemod.json +13 -0
@@ -0,0 +1,2318 @@
1
+ import { Node, Project, QuoteKind, SyntaxKind, ts, } from 'ts-morph';
2
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
3
+ import { createRequire } from 'node:module';
4
+ import { basename, dirname, extname, isAbsolute, join, resolve, } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+ const SUPPORTED_DECORATORS = {
7
+ Component: 'component',
8
+ Directive: 'directive',
9
+ Pipe: 'pipe',
10
+ Injectable: 'injectable',
11
+ };
12
+ const DEFAULT_OPTIONS = {
13
+ helperImportPath: '@craft-ts/core',
14
+ transformOnlyStandaloneDeclarables: false,
15
+ includeProviders: true,
16
+ includeViewProviders: true,
17
+ config: undefined,
18
+ configFilePath: undefined,
19
+ };
20
+ const CRAFT_DEV_TOOLS_CONFIG_FILE_NAME = 'craft-dev-tools.config.ts';
21
+ const ANGULAR_BRAND_CONFIG_FILE_NAME = 'craft-brand.config.ts';
22
+ const DEFAULT_ANGULAR_BRAND_METADATA_CONTEXTS = [
23
+ 'imports',
24
+ 'hostDirectives',
25
+ ];
26
+ const DEFAULT_ANGULAR_BRAND_CONFIG = defineAngularBrandConfig({
27
+ importAugmentations: [
28
+ {
29
+ match: {
30
+ module: '@angular/router',
31
+ },
32
+ deps: [
33
+ {
34
+ key: 'Router',
35
+ symbol: 'Router',
36
+ module: '@angular/router',
37
+ },
38
+ ],
39
+ missingProvider: [
40
+ {
41
+ key: 'Router',
42
+ symbol: 'Router',
43
+ module: '@angular/router',
44
+ },
45
+ ],
46
+ },
47
+ {
48
+ match: {
49
+ module: '@angular/forms/signals',
50
+ symbols: ['FormField'],
51
+ metadata: ['imports'],
52
+ },
53
+ deps: [
54
+ {
55
+ key: 'FormField',
56
+ symbol: 'FormField',
57
+ typeText: 'FormField<never>',
58
+ module: '@angular/forms/signals',
59
+ },
60
+ ],
61
+ },
62
+ {
63
+ match: {
64
+ module: '@angular/forms/signals',
65
+ symbols: ['FormRoot'],
66
+ metadata: ['imports'],
67
+ },
68
+ deps: [
69
+ {
70
+ key: 'FormRoot',
71
+ symbol: 'FormRoot',
72
+ typeText: 'FormRoot<unknown>',
73
+ module: '@angular/forms/signals',
74
+ },
75
+ ],
76
+ },
77
+ {
78
+ match: {
79
+ module: '@craft-ts/core',
80
+ symbols: ['LegacyCraftFieldDirective'],
81
+ metadata: ['imports'],
82
+ },
83
+ deps: [
84
+ {
85
+ key: 'LegacyCraftFieldDirective',
86
+ symbol: 'LegacyCraftFieldDirective',
87
+ typeText: 'LegacyCraftFieldDirective<unknown>',
88
+ module: '@craft-ts/core',
89
+ },
90
+ ],
91
+ },
92
+ {
93
+ match: {
94
+ module: '@craft-ts/core',
95
+ symbols: ['LegacyCraftRouterLink'],
96
+ metadata: ['imports'],
97
+ },
98
+ deps: [
99
+ {
100
+ key: 'LegacyCraftRouterLink',
101
+ symbol: 'LegacyCraftRouterLink',
102
+ module: '@craft-ts/core',
103
+ },
104
+ ],
105
+ missingProvider: [
106
+ {
107
+ key: 'Router',
108
+ symbol: 'Router',
109
+ module: '@angular/router',
110
+ },
111
+ ],
112
+ },
113
+ ],
114
+ });
115
+ const angularBrandConfigCache = new Map();
116
+ const PRIMITIVE_TYPE_TEXTS = new Set([
117
+ 'any',
118
+ 'bigint',
119
+ 'boolean',
120
+ 'false',
121
+ 'never',
122
+ 'null',
123
+ 'number',
124
+ 'object',
125
+ 'string',
126
+ 'symbol',
127
+ 'true',
128
+ 'undefined',
129
+ 'unknown',
130
+ 'void',
131
+ 'Array',
132
+ 'Boolean',
133
+ 'Number',
134
+ 'Object',
135
+ 'ReadonlyArray',
136
+ 'String',
137
+ ]);
138
+ const GENERATED_FILE_SUFFIXES = [
139
+ '.d.ts',
140
+ '.gen.ts',
141
+ '.generated.ts',
142
+ '.ngfactory.ts',
143
+ '.ngsummary.ts',
144
+ '.ngtypecheck.ts',
145
+ ];
146
+ const IGNORED_DIRECTORIES = new Set([
147
+ '.angular',
148
+ '.git',
149
+ '.nx',
150
+ '.turbo',
151
+ 'coverage',
152
+ 'dist',
153
+ 'node_modules',
154
+ 'out-tsc',
155
+ 'tmp',
156
+ ]);
157
+ export function defineAngularBrandConfig(config) {
158
+ return config;
159
+ }
160
+ export function defineCraftDevToolsConfig(config) {
161
+ return config;
162
+ }
163
+ function isCraftDevToolsConfig(value) {
164
+ return Boolean(value &&
165
+ typeof value === 'object' &&
166
+ ('brand' in value || 'serviceMigration' in value));
167
+ }
168
+ export function discoverAngularBrandConfigFilePath(searchFromDir, stopDir) {
169
+ const resolvedStopDir = stopDir ? resolve(stopDir) : undefined;
170
+ let currentDir = resolve(searchFromDir);
171
+ while (true) {
172
+ for (const fileName of [
173
+ CRAFT_DEV_TOOLS_CONFIG_FILE_NAME,
174
+ ANGULAR_BRAND_CONFIG_FILE_NAME,
175
+ ]) {
176
+ const candidatePath = join(currentDir, fileName);
177
+ if (existsSync(candidatePath) && statSync(candidatePath).isFile()) {
178
+ return candidatePath;
179
+ }
180
+ }
181
+ if (resolvedStopDir && currentDir === resolvedStopDir) {
182
+ return undefined;
183
+ }
184
+ const parentDir = dirname(currentDir);
185
+ if (parentDir === currentDir) {
186
+ return undefined;
187
+ }
188
+ currentDir = parentDir;
189
+ }
190
+ }
191
+ export function loadAngularBrandConfigFromFile(configFilePath) {
192
+ const resolvedConfigFilePath = resolve(configFilePath);
193
+ const cachedConfig = angularBrandConfigCache.get(resolvedConfigFilePath);
194
+ if (cachedConfig) {
195
+ return cachedConfig;
196
+ }
197
+ if (!existsSync(resolvedConfigFilePath) ||
198
+ !statSync(resolvedConfigFilePath).isFile()) {
199
+ throw new Error(`Angular brand config file not found at "${resolvedConfigFilePath}".`);
200
+ }
201
+ try {
202
+ const sourceText = readFileSync(resolvedConfigFilePath, 'utf8');
203
+ const transpiled = ts.transpileModule(sourceText, {
204
+ compilerOptions: {
205
+ module: ts.ModuleKind.CommonJS,
206
+ target: ts.ScriptTarget.ES2022,
207
+ esModuleInterop: true,
208
+ allowSyntheticDefaultImports: true,
209
+ },
210
+ fileName: resolvedConfigFilePath,
211
+ });
212
+ const module = { exports: {} };
213
+ const moduleRequire = createRequire(resolvedConfigFilePath);
214
+ const compiledModule = new Function('exports', 'require', 'module', '__filename', '__dirname', transpiled.outputText);
215
+ compiledModule(module.exports, (specifier) => {
216
+ if (specifier === '@craft-ts/dev-tools') {
217
+ return { defineAngularBrandConfig, defineCraftDevToolsConfig };
218
+ }
219
+ return moduleRequire(specifier);
220
+ }, module, resolvedConfigFilePath, dirname(resolvedConfigFilePath));
221
+ const exportedConfig = module.exports['default'] ??
222
+ (module.exports['__esModule']
223
+ ? module.exports['default']
224
+ : module.exports);
225
+ const brandConfig = isCraftDevToolsConfig(exportedConfig)
226
+ ? (exportedConfig.brand ?? {})
227
+ : exportedConfig;
228
+ const validatedConfig = validateAngularBrandConfig(brandConfig, resolvedConfigFilePath);
229
+ angularBrandConfigCache.set(resolvedConfigFilePath, validatedConfig);
230
+ return validatedConfig;
231
+ }
232
+ catch (error) {
233
+ throw new Error(`Invalid Angular brand config at "${resolvedConfigFilePath}": ${getErrorMessage(error)}`);
234
+ }
235
+ }
236
+ function resolveAngularBrandConfig(sourceFile, options) {
237
+ const explicitConfig = options.config ??
238
+ (options.configFilePath
239
+ ? loadAngularBrandConfigFromFile(options.configFilePath)
240
+ : loadDiscoveredAngularBrandConfig(dirname(sourceFile.getFilePath())));
241
+ return normalizeAngularBrandConfig(explicitConfig);
242
+ }
243
+ function loadDiscoveredAngularBrandConfig(searchFromDir, stopDir) {
244
+ const discoveredConfigPath = discoverAngularBrandConfigFilePath(searchFromDir, stopDir);
245
+ return discoveredConfigPath
246
+ ? loadAngularBrandConfigFromFile(discoveredConfigPath)
247
+ : undefined;
248
+ }
249
+ function normalizeAngularBrandConfig(config) {
250
+ const builtInRules = (DEFAULT_ANGULAR_BRAND_CONFIG.importAugmentations ?? []).map(normalizeAngularBrandImportAugmentationRule);
251
+ const configuredRules = (config?.importAugmentations ?? []).map(normalizeAngularBrandImportAugmentationRule);
252
+ return {
253
+ importAugmentations: [...builtInRules, ...configuredRules],
254
+ };
255
+ }
256
+ function normalizeAngularBrandImportAugmentationRule(rule) {
257
+ return {
258
+ match: {
259
+ module: rule.match.module,
260
+ symbols: rule.match.symbols ? [...rule.match.symbols] : undefined,
261
+ metadata: rule.match.metadata
262
+ ? [...rule.match.metadata]
263
+ : [...DEFAULT_ANGULAR_BRAND_METADATA_CONTEXTS],
264
+ },
265
+ deps: rule.deps ? [...rule.deps] : [],
266
+ missingProvider: rule.missingProvider ? [...rule.missingProvider] : [],
267
+ };
268
+ }
269
+ function validateAngularBrandConfig(value, configFilePath) {
270
+ if (!isPlainObject(value)) {
271
+ throw new Error('Expected the default export to be an object.');
272
+ }
273
+ const importAugmentations = readOptionalArray(value['importAugmentations'], 'importAugmentations', configFilePath)?.map((entry, index) => validateAngularBrandImportAugmentationRule(entry, `importAugmentations[${index}]`, configFilePath));
274
+ return {
275
+ importAugmentations: importAugmentations ?? [],
276
+ };
277
+ }
278
+ function validateAngularBrandImportAugmentationRule(value, pathLabel, configFilePath) {
279
+ if (!isPlainObject(value)) {
280
+ throw new Error(`${pathLabel} must be an object.`);
281
+ }
282
+ if (!isPlainObject(value['match'])) {
283
+ throw new Error(`${pathLabel}.match must be an object.`);
284
+ }
285
+ const match = value['match'];
286
+ const moduleSpecifier = readRequiredString(match['module'], `${pathLabel}.match.module`, configFilePath);
287
+ const symbols = readOptionalStringArray(match['symbols'], `${pathLabel}.match.symbols`, configFilePath);
288
+ const metadata = readOptionalStringArray(match['metadata'], `${pathLabel}.match.metadata`, configFilePath)?.map((context) => validateAngularBrandMetadataContext(context, pathLabel)) ?? undefined;
289
+ return {
290
+ match: {
291
+ module: moduleSpecifier,
292
+ symbols,
293
+ metadata,
294
+ },
295
+ deps: readOptionalArray(value['deps'], `${pathLabel}.deps`, configFilePath)?.map((entry, index) => validateAngularBrandConfigEntry(entry, `${pathLabel}.deps[${index}]`, configFilePath)) ?? [],
296
+ missingProvider: readOptionalArray(value['missingProvider'], `${pathLabel}.missingProvider`, configFilePath)?.map((entry, index) => validateAngularBrandConfigEntry(entry, `${pathLabel}.missingProvider[${index}]`, configFilePath)) ?? [],
297
+ };
298
+ }
299
+ function validateAngularBrandConfigEntry(value, pathLabel, configFilePath) {
300
+ if (!isPlainObject(value)) {
301
+ throw new Error(`${pathLabel} must be an object.`);
302
+ }
303
+ const key = readRequiredString(value['key'], `${pathLabel}.key`, configFilePath);
304
+ const symbol = readRequiredString(value['symbol'], `${pathLabel}.symbol`, configFilePath);
305
+ const typeText = readOptionalString(value['typeText'], `${pathLabel}.typeText`, configFilePath);
306
+ const moduleSpecifier = readOptionalString(value['module'], `${pathLabel}.module`, configFilePath);
307
+ return {
308
+ key,
309
+ symbol,
310
+ typeText,
311
+ module: moduleSpecifier,
312
+ };
313
+ }
314
+ function validateAngularBrandMetadataContext(value, pathLabel) {
315
+ if (value === 'imports' || value === 'hostDirectives') {
316
+ return value;
317
+ }
318
+ throw new Error(`${pathLabel}.match.metadata must contain only "imports" or "hostDirectives".`);
319
+ }
320
+ function readOptionalArray(value, pathLabel, configFilePath) {
321
+ if (value === undefined) {
322
+ return undefined;
323
+ }
324
+ if (!Array.isArray(value)) {
325
+ throw new Error(`${pathLabel} in "${configFilePath}" must be an array.`);
326
+ }
327
+ return value;
328
+ }
329
+ function readRequiredString(value, pathLabel, configFilePath) {
330
+ const stringValue = readOptionalString(value, pathLabel, configFilePath);
331
+ if (!stringValue) {
332
+ throw new Error(`${pathLabel} in "${configFilePath}" must be a string.`);
333
+ }
334
+ return stringValue;
335
+ }
336
+ function readOptionalString(value, pathLabel, configFilePath) {
337
+ if (value === undefined) {
338
+ return undefined;
339
+ }
340
+ if (typeof value !== 'string') {
341
+ throw new Error(`${pathLabel} in "${configFilePath}" must be a string.`);
342
+ }
343
+ return value;
344
+ }
345
+ function readOptionalStringArray(value, pathLabel, configFilePath) {
346
+ const arrayValue = readOptionalArray(value, pathLabel, configFilePath);
347
+ if (!arrayValue) {
348
+ return undefined;
349
+ }
350
+ return arrayValue.map((entry, index) => readRequiredString(entry, `${pathLabel}[${index}]`, configFilePath));
351
+ }
352
+ function isPlainObject(value) {
353
+ return value !== null && typeof value === 'object' && !Array.isArray(value);
354
+ }
355
+ function getErrorMessage(error) {
356
+ return error instanceof Error ? error.message : String(error);
357
+ }
358
+ export function transformSourceFile(sourceFile, options = {}) {
359
+ const normalizedOptions = normalizeOptions(options);
360
+ const analysis = analyzeSourceFileDependencies(sourceFile, options);
361
+ const result = {
362
+ ...analysis,
363
+ changed: false,
364
+ };
365
+ const { angularKind, classDeclaration, className } = analysis;
366
+ if (analysis.skipped || !classDeclaration || !angularKind || !className) {
367
+ return result;
368
+ }
369
+ const importSafety = getHelperImportSafety(sourceFile, normalizedOptions.helperImportPath);
370
+ if (!importSafety.safe) {
371
+ return skip(result, importSafety.warnings);
372
+ }
373
+ const beforeText = sourceFile.getFullText();
374
+ migrateLegacyBrandExport(sourceFile, classDeclaration, className);
375
+ ensureHelperImports(sourceFile, normalizedOptions.helperImportPath);
376
+ ensureGeneratedDependencyTypeImports(sourceFile, result.generatedDependencyGroups);
377
+ writeGeneratedDepsTypeAlias(sourceFile, angularKind, className, result.generatedDependencyGroups);
378
+ sourceFile.organizeImports();
379
+ result.changed = sourceFile.getFullText() !== beforeText;
380
+ return result;
381
+ }
382
+ export function analyzeSourceFileDependencies(sourceFile, options = {}) {
383
+ const normalizedOptions = normalizeOptions(options);
384
+ const angularBrandConfig = resolveAngularBrandConfig(sourceFile, normalizedOptions);
385
+ const result = {
386
+ skipped: false,
387
+ warnings: [],
388
+ dependencies: [],
389
+ dependencyGroups: emptyDependencyGroups(),
390
+ generatedDependencyGroups: emptyGeneratedDependencyGroups(),
391
+ };
392
+ const angularClass = findAngularDecoratedClass(sourceFile);
393
+ result.warnings.push(...angularClass.warnings);
394
+ result.angularKind = angularClass.angularKind;
395
+ result.className = angularClass.className;
396
+ result.classDeclaration = angularClass.classDeclaration;
397
+ if (angularClass.skipped) {
398
+ result.skipped = true;
399
+ return result;
400
+ }
401
+ if (!angularClass.classDeclaration) {
402
+ return result;
403
+ }
404
+ const { angularKind, classDeclaration, className } = angularClass;
405
+ if (!angularKind || !className) {
406
+ result.skipped = true;
407
+ result.warnings.push('Angular class name or kind could not be resolved.');
408
+ return result;
409
+ }
410
+ if (normalizedOptions.transformOnlyStandaloneDeclarables &&
411
+ (angularKind === 'component' || angularKind === 'directive') &&
412
+ !isStandaloneDeclarable(classDeclaration)) {
413
+ result.skipped = true;
414
+ result.warnings.push(`${decoratorLabel(angularKind)} ${className} is not standalone and transformOnlyStandaloneDeclarables is enabled.`);
415
+ return result;
416
+ }
417
+ const metadataDeps = angularKind === 'component' || angularKind === 'directive'
418
+ ? extractDecoratorMetadataDepGroups(classDeclaration, angularKind, normalizedOptions)
419
+ : emptyMetadataDependencyGroups();
420
+ const providedDeps = angularKind === 'component' || angularKind === 'directive'
421
+ ? extractProvidedDependencies(sourceFile, classDeclaration, angularKind, normalizedOptions)
422
+ : { entries: [], warnings: [] };
423
+ const constructorDeps = extractConstructorDeps(classDeclaration);
424
+ const propertyDeps = extractPropertyDependencies(sourceFile, classDeclaration, className);
425
+ const generatedDependencyAugmentation = createGeneratedDependencyGroupAugmentation(sourceFile, metadataDeps.occurrences, angularBrandConfig);
426
+ const syntheticDependencyTexts = mergeDeps(generatedDependencyAugmentation.deps.map((dependency) => dependency.dependencyText), generatedDependencyAugmentation.legacyInjectedDependencies.map((dependency) => dependency.dependencyText));
427
+ result.warnings.push(...metadataDeps.warnings, ...providedDeps.warnings, ...constructorDeps.warnings, ...propertyDeps.warnings);
428
+ result.dependencies = mergeDeps(constructorDeps.dependencies, propertyDeps.dependencies, metadataDeps.imports, metadataDeps.hostDirectives, metadataDeps.providers, metadataDeps.viewProviders, syntheticDependencyTexts);
429
+ result.dependencyGroups = {
430
+ injected: mergeDeps(constructorDeps.dependencies, propertyDeps.dependencies, generatedDependencyAugmentation.legacyInjectedDependencies.map((dependency) => dependency.dependencyText)),
431
+ importDeps: mergeDeps(metadataDeps.imports, metadataDeps.hostDirectives),
432
+ providers: mergeDeps(metadataDeps.providers, metadataDeps.viewProviders),
433
+ };
434
+ result.generatedTypeName = getGeneratedDepsTypeName(className);
435
+ result.generatedDependencyGroups = createGeneratedDependencyGroups(sourceFile, className, result.dependencyGroups.importDeps, constructorDeps.dependencies, propertyDeps, providedDeps.entries, generatedDependencyAugmentation);
436
+ return result;
437
+ }
438
+ export function findAngularDecoratedClass(sourceFile) {
439
+ const decoratedClasses = sourceFile
440
+ .getClasses()
441
+ .map((classDeclaration) => {
442
+ const angularKind = getAngularKind(classDeclaration);
443
+ return angularKind ? { classDeclaration, angularKind } : undefined;
444
+ })
445
+ .filter((entry) => Boolean(entry));
446
+ if (decoratedClasses.length === 0) {
447
+ return { skipped: false, warnings: [] };
448
+ }
449
+ if (decoratedClasses.length > 1) {
450
+ return {
451
+ skipped: true,
452
+ warnings: [
453
+ `Skipped file because it contains ${decoratedClasses.length} supported Angular classes.`,
454
+ ],
455
+ };
456
+ }
457
+ const [{ classDeclaration, angularKind }] = decoratedClasses;
458
+ const className = classDeclaration.getName();
459
+ if (!className) {
460
+ return {
461
+ classDeclaration,
462
+ angularKind,
463
+ skipped: true,
464
+ warnings: ['Skipped anonymous default-exported Angular class.'],
465
+ };
466
+ }
467
+ return {
468
+ classDeclaration,
469
+ angularKind,
470
+ className,
471
+ skipped: false,
472
+ warnings: [],
473
+ };
474
+ }
475
+ export function getAngularKind(classDeclaration) {
476
+ for (const decorator of classDeclaration.getDecorators()) {
477
+ const decoratorName = getDecoratorName(decorator);
478
+ if (decoratorName && decoratorName in SUPPORTED_DECORATORS) {
479
+ return SUPPORTED_DECORATORS[decoratorName];
480
+ }
481
+ }
482
+ return undefined;
483
+ }
484
+ export function isStandaloneDeclarable(classDeclaration) {
485
+ const angularKind = getAngularKind(classDeclaration);
486
+ if (angularKind !== 'component' && angularKind !== 'directive') {
487
+ return false;
488
+ }
489
+ const metadata = getDecoratorMetadataObject(classDeclaration);
490
+ const standaloneProperty = metadata
491
+ ? getObjectPropertyAssignment(metadata, 'standalone')
492
+ : undefined;
493
+ const initializer = standaloneProperty?.getInitializer();
494
+ return Node.isTrueLiteral(initializer);
495
+ }
496
+ export function extractDecoratorMetadataDeps(classDeclaration, angularKind = getAngularKind(classDeclaration), options = {}) {
497
+ if (angularKind !== 'component' && angularKind !== 'directive') {
498
+ return { dependencies: [], warnings: [] };
499
+ }
500
+ const groups = extractDecoratorMetadataDepGroups(classDeclaration, angularKind, normalizeOptions(options));
501
+ return {
502
+ dependencies: mergeDeps(groups.imports, groups.hostDirectives, groups.providers, groups.viewProviders),
503
+ warnings: groups.warnings,
504
+ };
505
+ }
506
+ export function extractConstructorDeps(classDeclaration) {
507
+ const warnings = [];
508
+ const dependencies = [];
509
+ for (const constructorDeclaration of classDeclaration.getConstructors()) {
510
+ for (const parameter of constructorDeclaration.getParameters()) {
511
+ const injectToken = getInjectDecoratorToken(parameter.getDecorators(), warnings);
512
+ if (injectToken.found) {
513
+ if (injectToken.dependency) {
514
+ dependencies.push(injectToken.dependency);
515
+ }
516
+ continue;
517
+ }
518
+ const typeNode = parameter.getTypeNode();
519
+ if (!typeNode) {
520
+ warnings.push(`Skipped constructor parameter "${parameter.getName()}" because it has no type.`);
521
+ continue;
522
+ }
523
+ const dependency = getDependencyTextFromTypeNode(typeNode);
524
+ if (!dependency) {
525
+ warnings.push(`Skipped constructor parameter "${parameter.getName()}" because type "${typeNode.getText()}" is not a static Angular dependency.`);
526
+ continue;
527
+ }
528
+ if (!isRuntimeSafeTypeDependency(typeNode, dependency)) {
529
+ warnings.push(`Skipped constructor dependency "${dependency}" because it is unresolved, type-only, or not a runtime value.`);
530
+ continue;
531
+ }
532
+ dependencies.push(dependency);
533
+ }
534
+ }
535
+ return { dependencies: mergeDeps(dependencies), warnings };
536
+ }
537
+ export function extractInjectCallDeps(classDeclaration) {
538
+ const warnings = [];
539
+ const dependencies = [];
540
+ const generatedDependencies = [];
541
+ const sourceFile = classDeclaration.getSourceFile();
542
+ for (const callExpression of classDeclaration.getDescendantsOfKind(SyntaxKind.CallExpression)) {
543
+ const expression = callExpression.getExpression();
544
+ const injectMethodName = getInjectMethodName(expression);
545
+ if (!injectMethodName ||
546
+ (injectMethodName !== 'inject' &&
547
+ !resolveTrackedInjectHelper(expression))) {
548
+ continue;
549
+ }
550
+ const dependency = injectMethodName === 'inject'
551
+ ? getAngularInjectCallDependency(callExpression)
552
+ : getInjectionHelperDependency(expression);
553
+ if (!dependency) {
554
+ warnings.push(`Skipped ${injectMethodName}() call in ${classDeclaration.getName() ?? 'anonymous class'} because the injection dependency is not static.`);
555
+ continue;
556
+ }
557
+ dependencies.push(dependency);
558
+ generatedDependencies.push({
559
+ dependencyText: dependency,
560
+ entry: injectMethodName === 'inject'
561
+ ? createGeneratedDependencyEntry(sourceFile, dependency, 'inject')
562
+ : createGeneratedInjectHelperDependencyEntry(sourceFile, callExpression, expression, dependency),
563
+ });
564
+ }
565
+ return {
566
+ dependencies: mergeDeps(dependencies),
567
+ warnings,
568
+ generatedDependencies,
569
+ };
570
+ }
571
+ function extractPropertyDependencies(sourceFile, classDeclaration, className) {
572
+ const warnings = [];
573
+ const dependencies = [];
574
+ const entries = [];
575
+ const missingProvider = [];
576
+ for (const property of classDeclaration.getProperties()) {
577
+ if (property.isStatic()) {
578
+ continue;
579
+ }
580
+ const propertyName = getStaticPropertyName(property.getNameNode());
581
+ if (!propertyName) {
582
+ warnings.push(`Skipped property dependency tracking for ${property.getText()} because the property name is not static.`);
583
+ continue;
584
+ }
585
+ const initializer = property.getInitializer();
586
+ if (Node.isCallExpression(initializer)) {
587
+ const expression = initializer.getExpression();
588
+ const injectMethodName = getInjectMethodName(expression);
589
+ if (injectMethodName &&
590
+ (injectMethodName === 'inject' ||
591
+ resolveTrackedInjectHelper(expression))) {
592
+ const dependencyText = injectMethodName === 'inject'
593
+ ? getAngularInjectCallDependency(initializer)
594
+ : getInjectionHelperDependency(expression);
595
+ if (!dependencyText) {
596
+ warnings.push(`Skipped ${injectMethodName}() property dependency tracking for "${propertyName}" because the dependency is not static.`);
597
+ }
598
+ else {
599
+ dependencies.push(dependencyText);
600
+ const dependencyEntry = injectMethodName === 'inject'
601
+ ? createGeneratedDependencyEntry(sourceFile, dependencyText, 'inject')
602
+ : createGeneratedInjectHelperDependencyEntry(sourceFile, initializer, expression, dependencyText);
603
+ entries.push(createGeneratedPropertyDependencyEntry(propertyName, createSingleDependencyMapTypeText(dependencyEntry)));
604
+ if (shouldGenerateLocalMissingProvider(sourceFile, dependencyText)) {
605
+ missingProvider.push(createGeneratedDependencyEntry(sourceFile, dependencyText, 'inject'));
606
+ }
607
+ continue;
608
+ }
609
+ }
610
+ }
611
+ entries.push(createGeneratedPropertyDependencyEntry(propertyName, createExtractDepsTypeText(className, propertyName)));
612
+ }
613
+ return {
614
+ dependencies: mergeDeps(dependencies),
615
+ warnings,
616
+ entries,
617
+ missingProvider: mergeGeneratedDependencyEntries(missingProvider),
618
+ };
619
+ }
620
+ export function mergeDeps(...dependencyGroups) {
621
+ const seen = new Set();
622
+ const dependencies = [];
623
+ for (const group of dependencyGroups) {
624
+ for (const dependency of group) {
625
+ if (seen.has(dependency)) {
626
+ continue;
627
+ }
628
+ seen.add(dependency);
629
+ dependencies.push(dependency);
630
+ }
631
+ }
632
+ return dependencies;
633
+ }
634
+ function emptyDependencyGroups() {
635
+ return {
636
+ injected: [],
637
+ importDeps: [],
638
+ providers: [],
639
+ };
640
+ }
641
+ function emptyGeneratedDependencyGroups() {
642
+ return {
643
+ deps: [],
644
+ propertiesDeps: [],
645
+ provided: [],
646
+ missingProvider: [],
647
+ };
648
+ }
649
+ function emptyGeneratedDependencyGroupAugmentation() {
650
+ return {
651
+ deps: [],
652
+ missingProvider: [],
653
+ legacyInjectedDependencies: [],
654
+ suppressedImportDependencyTexts: new Set(),
655
+ };
656
+ }
657
+ function getGeneratedDepsTypeName(className) {
658
+ return `GenDeps_${className}`;
659
+ }
660
+ function createGeneratedDependencyGroups(sourceFile, className, importDependencies, constructorDependencies, propertyDependencies, providedEntries, augmentation) {
661
+ const constructorEntries = constructorDependencies.map((dependency) => createGeneratedDependencyEntry(sourceFile, dependency, 'inject'));
662
+ const deps = mergeGeneratedDependencyEntries(importDependencies
663
+ .filter((dependency) => !augmentation.suppressedImportDependencyTexts.has(dependency))
664
+ .map((dependency) => createGeneratedDependencyEntry(sourceFile, dependency, 'import')), constructorEntries, augmentation.deps.map((dependency) => dependency.entry));
665
+ const propertiesDeps = mergeGeneratedDependencyEntries(propertyDependencies.entries);
666
+ const provided = mergeGeneratedDependencyEntries(providedEntries);
667
+ const providedKeys = new Set(provided.map((entry) => entry.key));
668
+ const missingProvider = mergeGeneratedDependencyEntries(constructorDependencies
669
+ .filter((dependencyText) => shouldGenerateLocalMissingProvider(sourceFile, dependencyText))
670
+ .map((dependencyText) => createGeneratedDependencyEntry(sourceFile, dependencyText, 'inject')), propertyDependencies.missingProvider, augmentation.missingProvider
671
+ .map((dependency) => dependency.entry)
672
+ .filter((entry) => !providedKeys.has(entry.key)));
673
+ return {
674
+ deps,
675
+ propertiesDeps,
676
+ provided,
677
+ missingProvider,
678
+ };
679
+ }
680
+ function createGeneratedDependencyGroupAugmentation(sourceFile, metadataOccurrences, config) {
681
+ if (metadataOccurrences.length === 0) {
682
+ return emptyGeneratedDependencyGroupAugmentation();
683
+ }
684
+ const deps = [];
685
+ const missingProvider = [];
686
+ const legacyInjectedDependencies = [];
687
+ const suppressedImportDependencyTexts = new Set();
688
+ for (const rule of config.importAugmentations) {
689
+ const matchingOccurrences = metadataOccurrences.filter((occurrence) => ruleMatchesMetadataOccurrence(rule, occurrence));
690
+ if (matchingOccurrences.length === 0) {
691
+ continue;
692
+ }
693
+ const generatedDeps = rule.deps.map((entry) => createConfiguredGeneratedDependencyDescriptor(sourceFile, entry, rule.match.module));
694
+ const generatedMissingProviders = rule.missingProvider.map((entry) => createConfiguredGeneratedDependencyDescriptor(sourceFile, entry, rule.match.module));
695
+ deps.push(...generatedDeps);
696
+ missingProvider.push(...generatedMissingProviders);
697
+ legacyInjectedDependencies.push(...generatedMissingProviders);
698
+ const ruleEntryKeys = new Set(rule.deps.map((entry) => entry.key));
699
+ for (const occurrence of matchingOccurrences) {
700
+ if (occurrence.metadataContext === 'imports' &&
701
+ ruleEntryKeys.has(occurrence.symbolName)) {
702
+ suppressedImportDependencyTexts.add(occurrence.dependencyText);
703
+ }
704
+ }
705
+ }
706
+ return {
707
+ deps,
708
+ missingProvider,
709
+ legacyInjectedDependencies,
710
+ suppressedImportDependencyTexts,
711
+ };
712
+ }
713
+ function ruleMatchesMetadataOccurrence(rule, occurrence) {
714
+ if (occurrence.moduleSpecifier !== rule.match.module) {
715
+ return false;
716
+ }
717
+ if (!rule.match.metadata.includes(occurrence.metadataContext)) {
718
+ return false;
719
+ }
720
+ return (!rule.match.symbols || rule.match.symbols.includes(occurrence.symbolName));
721
+ }
722
+ function createConfiguredGeneratedDependencyDescriptor(sourceFile, entry, defaultModuleSpecifier) {
723
+ return {
724
+ dependencyText: entry.symbol,
725
+ entry: createSyntheticGeneratedDependencyEntry(sourceFile, entry.symbol, 'inject', entry.module ?? defaultModuleSpecifier, entry.key, entry.typeText),
726
+ };
727
+ }
728
+ function mergeGeneratedDependencyEntries(...groups) {
729
+ const entries = new Map();
730
+ for (const group of groups) {
731
+ for (const entry of group) {
732
+ entries.set(entry.key, entry);
733
+ }
734
+ }
735
+ return [...entries.values()];
736
+ }
737
+ function createGeneratedDependencyEntry(sourceFile, dependencyText, context) {
738
+ if (context === 'import') {
739
+ const declarableResolution = resolveAngularDeclarableDependency(sourceFile, dependencyText);
740
+ if (declarableResolution) {
741
+ const generatedTypeName = getGeneratedDepsTypeName(declarableResolution.className);
742
+ return {
743
+ key: generatedTypeName,
744
+ typeText: generatedTypeName,
745
+ typeImport: declarableResolution.moduleSpecifier
746
+ ? {
747
+ moduleSpecifier: declarableResolution.moduleSpecifier,
748
+ name: generatedTypeName,
749
+ }
750
+ : undefined,
751
+ };
752
+ }
753
+ }
754
+ return {
755
+ key: createGeneratedDependencyKey(dependencyText),
756
+ typeText: createGeneratedDependencyTypeText(sourceFile, dependencyText),
757
+ };
758
+ }
759
+ function createSyntheticGeneratedDependencyEntry(sourceFile, dependencyText, context, moduleSpecifier, key = createGeneratedDependencyKey(dependencyText), typeText) {
760
+ return {
761
+ ...createGeneratedDependencyEntry(sourceFile, dependencyText, context),
762
+ key,
763
+ typeText: typeText ?? createGeneratedDependencyTypeText(sourceFile, dependencyText),
764
+ typeImport: {
765
+ moduleSpecifier,
766
+ name: dependencyText,
767
+ },
768
+ };
769
+ }
770
+ function createGeneratedInjectHelperDependencyEntry(sourceFile, callExpression, expression, dependencyText) {
771
+ const trackedHelper = resolveTrackedInjectHelper(expression);
772
+ if (!trackedHelper) {
773
+ return createGeneratedDependencyEntry(sourceFile, dependencyText, 'inject');
774
+ }
775
+ const exposureTracking = extractHelperExposureTracking(callExpression);
776
+ return {
777
+ key: trackedHelper.serviceName,
778
+ typeText: createTrackedInjectHelperTypeText(dependencyText, trackedHelper.serviceName, exposureTracking),
779
+ };
780
+ }
781
+ function createTrackedInjectHelperTypeText(dependencyText, serviceName, exposureTracking) {
782
+ const baseType = `ExtractDeps<typeof ${dependencyText}>[${JSON.stringify(serviceName)}]`;
783
+ if (!exposureTracking) {
784
+ return baseType;
785
+ }
786
+ return [
787
+ `DerivedService<${baseType}, {`,
788
+ ` derivedPropertiesUsed: ${formatHelperExposurePropertiesType(dependencyText, exposureTracking.usedProperties.map((sourceKey) => ({
789
+ propertyKey: sourceKey,
790
+ sourceKey,
791
+ })))};`,
792
+ ` derivedPropertiesExposed: ${formatHelperExposurePropertiesType(dependencyText, exposureTracking.exposedProperties.map(({ exposedKey, sourceKey }) => ({
793
+ propertyKey: exposedKey,
794
+ sourceKey,
795
+ })))};`,
796
+ '}>',
797
+ ].join('\n');
798
+ }
799
+ function createGeneratedPropertyDependencyEntry(propertyName, typeText) {
800
+ return {
801
+ key: propertyName,
802
+ typeText,
803
+ };
804
+ }
805
+ function createExtractDepsTypeText(className, propertyName) {
806
+ return `ExtractDeps<${className}[${JSON.stringify(propertyName)}]>`;
807
+ }
808
+ function createSingleDependencyMapTypeText(entry) {
809
+ return formatGeneratedDependencyObject([entry]);
810
+ }
811
+ function formatHelperExposurePropertiesType(dependencyText, properties) {
812
+ if (properties.length === 0) {
813
+ return '{}';
814
+ }
815
+ return [
816
+ '{',
817
+ ...properties.map(({ propertyKey, sourceKey }) => ` ${formatObjectKey(propertyKey)}: ${createTrackedHelperOutputTypeText(dependencyText, sourceKey)};`),
818
+ ' }',
819
+ ].join('\n');
820
+ }
821
+ function createTrackedHelperOutputTypeText(dependencyText, sourceKey) {
822
+ if (sourceKey === '$self') {
823
+ return `GetServiceOutput<typeof ${dependencyText}>`;
824
+ }
825
+ return `GetServiceOutput<typeof ${dependencyText}>[${JSON.stringify(sourceKey)}]`;
826
+ }
827
+ function resolveTrackedInjectHelper(expression) {
828
+ const symbol = expression.getSymbol() ?? expression.getType().getSymbol();
829
+ const declarations = symbol?.getAliasedSymbol()?.getDeclarations() ??
830
+ symbol?.getDeclarations() ??
831
+ [];
832
+ for (const declaration of declarations) {
833
+ const helper = resolveTrackedInjectHelperFromDeclaration(declaration);
834
+ if (helper) {
835
+ return helper;
836
+ }
837
+ }
838
+ return undefined;
839
+ }
840
+ function resolveTrackedInjectHelperFromDeclaration(declaration) {
841
+ if (!Node.isBindingElement(declaration)) {
842
+ return undefined;
843
+ }
844
+ const objectBindingPattern = declaration.getFirstAncestorByKind(SyntaxKind.ObjectBindingPattern);
845
+ const variableDeclaration = objectBindingPattern?.getFirstAncestorByKind(SyntaxKind.VariableDeclaration);
846
+ const initializer = variableDeclaration?.getInitializer();
847
+ if (!Node.isCallExpression(initializer) ||
848
+ !isServiceFactoryCall(initializer)) {
849
+ return undefined;
850
+ }
851
+ const [optionsArgument] = initializer.getArguments();
852
+ if (!Node.isObjectLiteralExpression(optionsArgument)) {
853
+ return undefined;
854
+ }
855
+ const nameInitializer = getObjectPropertyAssignment(optionsArgument, 'name')?.getInitializer();
856
+ const scopeInitializer = getObjectPropertyAssignment(optionsArgument, 'providedIn')?.getInitializer();
857
+ if (!Node.isStringLiteral(nameInitializer) ||
858
+ !Node.isStringLiteral(scopeInitializer)) {
859
+ return undefined;
860
+ }
861
+ return {
862
+ serviceName: nameInitializer.getLiteralText(),
863
+ scope: scopeInitializer.getLiteralText(),
864
+ };
865
+ }
866
+ function isServiceFactoryCall(callExpression) {
867
+ const expression = callExpression.getExpression();
868
+ if (Node.isIdentifier(expression)) {
869
+ return (expression.getText() === 'craftService' ||
870
+ expression.getText() === 'toCraftService');
871
+ }
872
+ if (Node.isPropertyAccessExpression(expression)) {
873
+ const methodName = expression.getName();
874
+ return methodName === 'craftService' || methodName === 'toCraftService';
875
+ }
876
+ return false;
877
+ }
878
+ function extractHelperExposureTracking(callExpression) {
879
+ const exposeArgument = [...callExpression.getArguments()]
880
+ .reverse()
881
+ .find((argument) => Node.isArrowFunction(argument) || Node.isFunctionExpression(argument));
882
+ if (!exposeArgument ||
883
+ (!Node.isArrowFunction(exposeArgument) &&
884
+ !Node.isFunctionExpression(exposeArgument))) {
885
+ return undefined;
886
+ }
887
+ const parameter = exposeArgument.getParameters()[0];
888
+ const nameNode = parameter?.getNameNode();
889
+ if (!nameNode || !Node.isObjectBindingPattern(nameNode)) {
890
+ return undefined;
891
+ }
892
+ const bindings = extractHelperExposureBindings(nameNode);
893
+ if (bindings.length === 0) {
894
+ return undefined;
895
+ }
896
+ const returnObjectLiteral = getExposureReturnObjectLiteral(exposeArgument);
897
+ if (!returnObjectLiteral) {
898
+ return undefined;
899
+ }
900
+ const exposedProperties = extractHelperExposedProperties(returnObjectLiteral, bindings);
901
+ const usedProperties = mergeDeps(exposedProperties.map(({ sourceKey }) => sourceKey), extractHelperYieldedProperties(exposeArgument, bindings));
902
+ return {
903
+ usedProperties,
904
+ exposedProperties,
905
+ };
906
+ }
907
+ function extractHelperExposureBindings(bindingPattern) {
908
+ const bindings = [];
909
+ for (const element of bindingPattern.getElements()) {
910
+ const nameNode = element.getNameNode();
911
+ if (!Node.isIdentifier(nameNode)) {
912
+ continue;
913
+ }
914
+ const propertyNameNode = element.getPropertyNameNode();
915
+ const sourceKey = getStaticPropertyName(propertyNameNode ?? nameNode) ?? nameNode.getText();
916
+ bindings.push({
917
+ localName: nameNode.getText(),
918
+ sourceKey,
919
+ });
920
+ }
921
+ return bindings;
922
+ }
923
+ function getExposureReturnObjectLiteral(exposeArgument) {
924
+ const body = exposeArgument.getBody();
925
+ if (Node.isObjectLiteralExpression(body)) {
926
+ return body;
927
+ }
928
+ if (Node.isParenthesizedExpression(body)) {
929
+ const expression = body.getExpression();
930
+ return Node.isObjectLiteralExpression(expression) ? expression : undefined;
931
+ }
932
+ if (!Node.isBlock(body)) {
933
+ return undefined;
934
+ }
935
+ const returnStatement = [
936
+ ...body.getDescendantsOfKind(SyntaxKind.ReturnStatement),
937
+ ]
938
+ .reverse()
939
+ .find((statement) => Node.isObjectLiteralExpression(statement.getExpression()));
940
+ const expression = returnStatement?.getExpression();
941
+ return Node.isObjectLiteralExpression(expression) ? expression : undefined;
942
+ }
943
+ function extractHelperExposedProperties(objectLiteral, bindings) {
944
+ const bindingMap = new Map(bindings.map((binding) => [binding.localName, binding.sourceKey]));
945
+ const exposedProperties = [];
946
+ for (const property of objectLiteral.getProperties()) {
947
+ if (Node.isShorthandPropertyAssignment(property)) {
948
+ const sourceKey = bindingMap.get(property.getName());
949
+ if (sourceKey) {
950
+ exposedProperties.push({
951
+ exposedKey: property.getName(),
952
+ sourceKey,
953
+ });
954
+ }
955
+ continue;
956
+ }
957
+ if (!Node.isPropertyAssignment(property)) {
958
+ continue;
959
+ }
960
+ const exposedKey = getStaticPropertyName(property.getNameNode());
961
+ const initializer = property.getInitializer();
962
+ if (!exposedKey || !Node.isIdentifier(initializer)) {
963
+ continue;
964
+ }
965
+ const sourceKey = bindingMap.get(initializer.getText());
966
+ if (!sourceKey) {
967
+ continue;
968
+ }
969
+ exposedProperties.push({
970
+ exposedKey,
971
+ sourceKey,
972
+ });
973
+ }
974
+ return exposedProperties;
975
+ }
976
+ function extractHelperYieldedProperties(exposeArgument, bindings) {
977
+ const bindingMap = new Map(bindings.map((binding) => [binding.localName, binding.sourceKey]));
978
+ const yieldedProperties = [];
979
+ for (const yieldExpression of exposeArgument.getDescendantsOfKind(SyntaxKind.YieldExpression)) {
980
+ const expression = yieldExpression.getExpression();
981
+ if (!Node.isCallExpression(expression)) {
982
+ continue;
983
+ }
984
+ const target = expression.getExpression();
985
+ if (!Node.isIdentifier(target)) {
986
+ continue;
987
+ }
988
+ const sourceKey = bindingMap.get(target.getText());
989
+ if (sourceKey) {
990
+ yieldedProperties.push(sourceKey);
991
+ }
992
+ }
993
+ return yieldedProperties;
994
+ }
995
+ function createGeneratedDependencyKey(dependencyText) {
996
+ const lastSegment = dependencyText.split('.').pop() ?? dependencyText;
997
+ const helperMatch = /^(provide)([A-Z].*)$/.exec(lastSegment);
998
+ if (helperMatch) {
999
+ return helperMatch[2];
1000
+ }
1001
+ return lastSegment;
1002
+ }
1003
+ function createGeneratedDependencyTypeText(sourceFile, dependencyText) {
1004
+ if (isHelperLikeDependency(dependencyText)) {
1005
+ return `ReturnType<typeof ${dependencyText}>`;
1006
+ }
1007
+ const resolution = resolveDependencyReference(sourceFile, dependencyText);
1008
+ if (resolution.kind === 'class' || resolution.kind === 'enum') {
1009
+ return dependencyText;
1010
+ }
1011
+ if (resolution.kind === 'unknown') {
1012
+ return dependencyText;
1013
+ }
1014
+ return `typeof ${dependencyText}`;
1015
+ }
1016
+ function isHelperLikeDependency(dependencyText) {
1017
+ const lastSegment = dependencyText.split('.').pop() ?? dependencyText;
1018
+ return (/^provide[A-Z].*/.test(lastSegment));
1019
+ }
1020
+ function resolveAngularDeclarableDependency(sourceFile, dependencyText) {
1021
+ const resolution = resolveDependencyReference(sourceFile, dependencyText);
1022
+ if (!resolution.classDeclaration) {
1023
+ return undefined;
1024
+ }
1025
+ const angularKind = getAngularKind(resolution.classDeclaration);
1026
+ if (angularKind !== 'component' &&
1027
+ angularKind !== 'directive' &&
1028
+ angularKind !== 'pipe') {
1029
+ return undefined;
1030
+ }
1031
+ const className = resolution.classDeclaration.getName();
1032
+ if (!className) {
1033
+ return undefined;
1034
+ }
1035
+ const declarationSourceFile = resolution.classDeclaration.getSourceFile();
1036
+ if (declarationSourceFile === sourceFile ||
1037
+ declarationSourceFile.isDeclarationFile()) {
1038
+ return undefined;
1039
+ }
1040
+ return {
1041
+ className,
1042
+ moduleSpecifier: resolution.moduleSpecifier,
1043
+ };
1044
+ }
1045
+ function ensureGeneratedDependencyTypeImports(sourceFile, generatedDependencyGroups) {
1046
+ const importsToAdd = new Map();
1047
+ for (const entry of [
1048
+ ...generatedDependencyGroups.deps,
1049
+ ...generatedDependencyGroups.missingProvider,
1050
+ ]) {
1051
+ if (!entry.typeImport) {
1052
+ continue;
1053
+ }
1054
+ const importNames = importsToAdd.get(entry.typeImport.moduleSpecifier) ?? new Set();
1055
+ importNames.add(entry.typeImport.name);
1056
+ importsToAdd.set(entry.typeImport.moduleSpecifier, importNames);
1057
+ }
1058
+ for (const [moduleSpecifier, importNames] of importsToAdd) {
1059
+ const existingImport = sourceFile
1060
+ .getImportDeclarations()
1061
+ .find((importDeclaration) => importDeclaration.getModuleSpecifierValue() === moduleSpecifier &&
1062
+ !importDeclaration.getNamespaceImport());
1063
+ if (existingImport) {
1064
+ const existingImportNames = new Set(existingImport.getNamedImports().map((namedImport) => {
1065
+ return (namedImport.getAliasNode()?.getText() ??
1066
+ namedImport.getNameNode().getText());
1067
+ }));
1068
+ const namesToAdd = [...importNames].filter((name) => !existingImportNames.has(name));
1069
+ if (namesToAdd.length === 0) {
1070
+ continue;
1071
+ }
1072
+ const declarationIsTypeOnly = existingImport.isTypeOnly();
1073
+ existingImport.addNamedImports(namesToAdd.map((name) => ({
1074
+ name,
1075
+ isTypeOnly: !declarationIsTypeOnly,
1076
+ })));
1077
+ continue;
1078
+ }
1079
+ sourceFile.addImportDeclaration({
1080
+ moduleSpecifier,
1081
+ namedImports: [...importNames].map((name) => ({
1082
+ name,
1083
+ isTypeOnly: true,
1084
+ })),
1085
+ });
1086
+ }
1087
+ }
1088
+ function formatGeneratedDependencyType(angularKind, className, generatedDependencyGroups) {
1089
+ return [
1090
+ '{',
1091
+ ` deps: ${formatGeneratedDependencyObject(generatedDependencyGroups.deps)};`,
1092
+ ...(angularKind === 'component'
1093
+ ? [
1094
+ ` propertiesDeps: ${formatGeneratedDependencyObject(generatedDependencyGroups.propertiesDeps)};`,
1095
+ ]
1096
+ : []),
1097
+ ` provided: ${formatGeneratedDependencyObject(generatedDependencyGroups.provided)};`,
1098
+ ...(angularKind === 'component'
1099
+ ? [` publicProperties: GetPublicComponentProperties<${className}>;`]
1100
+ : []),
1101
+ ...(generatedDependencyGroups.missingProvider.length > 0
1102
+ ? [
1103
+ ` missingProvider: ${formatGeneratedDependencyObject(generatedDependencyGroups.missingProvider)};`,
1104
+ ]
1105
+ : []),
1106
+ '}',
1107
+ ].join('\n');
1108
+ }
1109
+ function formatGeneratedDependencyObject(entries) {
1110
+ if (entries.length === 0) {
1111
+ return '{}';
1112
+ }
1113
+ return [
1114
+ '{',
1115
+ ...entries.map((entry) => formatGeneratedDependencyEntry(entry)),
1116
+ ' }',
1117
+ ].join('\n');
1118
+ }
1119
+ function formatGeneratedDependencyEntry(entry) {
1120
+ const typeLines = entry.typeText.split('\n');
1121
+ const lines = [
1122
+ ` ${formatObjectKey(entry.key)}: ${typeLines[0]}`,
1123
+ ...typeLines.slice(1).map((line) => ` ${line}`),
1124
+ ];
1125
+ lines[lines.length - 1] += ';';
1126
+ return lines.join('\n');
1127
+ }
1128
+ function formatObjectKey(key) {
1129
+ return /^[$A-Z_][0-9A-Z_$]*$/i.test(key) ? key : `'${key}'`;
1130
+ }
1131
+ export function writeGeneratedDepsTypeAlias(sourceFile, angularKind, className, generatedDependencyGroups) {
1132
+ const generatedTypeName = getGeneratedDepsTypeName(className);
1133
+ const generatedType = `GetDeps<${formatGeneratedDependencyType(angularKind, className, generatedDependencyGroups)}>`;
1134
+ const existingTypeAlias = sourceFile.getTypeAlias(generatedTypeName);
1135
+ if (existingTypeAlias) {
1136
+ existingTypeAlias.setIsExported(true);
1137
+ existingTypeAlias.setType(generatedType);
1138
+ return;
1139
+ }
1140
+ sourceFile.addTypeAlias({
1141
+ isExported: true,
1142
+ name: generatedTypeName,
1143
+ type: generatedType,
1144
+ });
1145
+ }
1146
+ export function migrateLegacyBrandExport(sourceFile, classDeclaration, className) {
1147
+ const existing = readExistingDependencyGroups(sourceFile, className);
1148
+ if (!existing.found || !existing.exportAssignmentNode) {
1149
+ return;
1150
+ }
1151
+ const exportAssignment = existing.exportAssignmentNode.asKind(SyntaxKind.ExportAssignment);
1152
+ exportAssignment?.remove();
1153
+ if (!classDeclaration.isDefaultExport() &&
1154
+ !classDeclaration.isExported() &&
1155
+ exportAssignment) {
1156
+ classDeclaration.setIsDefaultExport(true);
1157
+ }
1158
+ }
1159
+ function invalidExistingDependencyGroups(node, warnings) {
1160
+ return {
1161
+ found: true,
1162
+ warnings,
1163
+ dependencyGroups: emptyDependencyGroups(),
1164
+ exportAssignmentNode: node,
1165
+ depsObjectNode: node,
1166
+ propertyNodes: {},
1167
+ };
1168
+ }
1169
+ function readExistingDependencyGroupProperty(objectLiteral, propertyName, warnings, propertyNodes) {
1170
+ const property = getObjectPropertyAssignment(objectLiteral, propertyName);
1171
+ if (!property) {
1172
+ warnings.push(`deps(...) is missing the "${propertyName}" array.`);
1173
+ return [];
1174
+ }
1175
+ propertyNodes[propertyName] = property;
1176
+ const initializer = property.getInitializer();
1177
+ if (!Node.isArrayLiteralExpression(initializer)) {
1178
+ warnings.push(`deps.${propertyName} must be a static array.`);
1179
+ return [];
1180
+ }
1181
+ const dependencies = [];
1182
+ for (const element of initializer.getElements()) {
1183
+ const dependency = getStaticExpressionText(element);
1184
+ if (!dependency) {
1185
+ warnings.push(`deps.${propertyName} contains a non-static expression: ${element.getText()}`);
1186
+ continue;
1187
+ }
1188
+ dependencies.push(dependency);
1189
+ }
1190
+ return dependencies;
1191
+ }
1192
+ function getInjectMethodName(expression) {
1193
+ if (Node.isIdentifier(expression)) {
1194
+ return expression.getText();
1195
+ }
1196
+ if (Node.isPropertyAccessExpression(expression)) {
1197
+ return expression.getName();
1198
+ }
1199
+ return undefined;
1200
+ }
1201
+ function getAngularInjectCallDependency(callExpression) {
1202
+ const [token] = callExpression.getArguments();
1203
+ return getStaticExpressionText(token);
1204
+ }
1205
+ function getInjectionHelperDependency(expression) {
1206
+ if (Node.isIdentifier(expression)) {
1207
+ return expression.getText();
1208
+ }
1209
+ if (Node.isPropertyAccessExpression(expression)) {
1210
+ const left = expression.getExpression();
1211
+ if (Node.isIdentifier(left)) {
1212
+ return expression.getText();
1213
+ }
1214
+ }
1215
+ return undefined;
1216
+ }
1217
+ export function ensureHelperImports(sourceFile, helperImportPath) {
1218
+ const missingImports = [
1219
+ 'DerivedService',
1220
+ 'ExtractDeps',
1221
+ 'GetDeps',
1222
+ 'GetPublicComponentProperties',
1223
+ 'GetServiceOutput',
1224
+ ].filter((importName) => !hasLocalNamedImport(sourceFile, helperImportPath, importName));
1225
+ if (missingImports.length === 0) {
1226
+ return;
1227
+ }
1228
+ const existingImport = sourceFile
1229
+ .getImportDeclarations()
1230
+ .find((importDeclaration) => importDeclaration.getModuleSpecifierValue() === helperImportPath);
1231
+ if (existingImport) {
1232
+ existingImport.addNamedImports(missingImports.map((name) => ({ name, isTypeOnly: true })));
1233
+ return;
1234
+ }
1235
+ sourceFile.addImportDeclaration({
1236
+ moduleSpecifier: helperImportPath,
1237
+ namedImports: missingImports.map((name) => ({
1238
+ name,
1239
+ isTypeOnly: true,
1240
+ })),
1241
+ });
1242
+ }
1243
+ export function readExistingDependencyGroups(sourceFile, className) {
1244
+ const defaultExport = sourceFile
1245
+ .getExportAssignments()
1246
+ .find((exportAssignment) => {
1247
+ if (exportAssignment.isExportEquals()) {
1248
+ return false;
1249
+ }
1250
+ const expression = exportAssignment.getExpression();
1251
+ if (!Node.isCallExpression(expression)) {
1252
+ return false;
1253
+ }
1254
+ const callTarget = expression.getExpression();
1255
+ if (!Node.isIdentifier(callTarget) ||
1256
+ callTarget.getText() !== 'brandAngularSymbol') {
1257
+ return false;
1258
+ }
1259
+ if (!className) {
1260
+ return true;
1261
+ }
1262
+ const [targetClass] = expression.getArguments();
1263
+ return (Node.isIdentifier(targetClass) && targetClass.getText() === className);
1264
+ });
1265
+ if (!defaultExport) {
1266
+ return {
1267
+ found: false,
1268
+ warnings: [],
1269
+ dependencyGroups: emptyDependencyGroups(),
1270
+ propertyNodes: {},
1271
+ };
1272
+ }
1273
+ const expression = defaultExport.getExpression();
1274
+ if (!Node.isCallExpression(expression)) {
1275
+ return invalidExistingDependencyGroups(defaultExport, [
1276
+ 'Default export must call brandAngularSymbol(ClassName, deps({ ... })).',
1277
+ ]);
1278
+ }
1279
+ const [, depsArgument] = expression.getArguments();
1280
+ if (!Node.isCallExpression(depsArgument)) {
1281
+ return invalidExistingDependencyGroups(defaultExport, [
1282
+ 'brandAngularSymbol must receive deps({ injected, importDeps, providers }).',
1283
+ ]);
1284
+ }
1285
+ const depsCallTarget = depsArgument.getExpression();
1286
+ if (!Node.isIdentifier(depsCallTarget) ||
1287
+ depsCallTarget.getText() !== 'deps') {
1288
+ return invalidExistingDependencyGroups(depsArgument, [
1289
+ 'brandAngularSymbol second argument must be a deps(...) call.',
1290
+ ]);
1291
+ }
1292
+ const [depsObject] = depsArgument.getArguments();
1293
+ if (!Node.isObjectLiteralExpression(depsObject)) {
1294
+ return invalidExistingDependencyGroups(depsArgument, [
1295
+ 'deps(...) must be called with an object literal.',
1296
+ ]);
1297
+ }
1298
+ const warnings = [];
1299
+ const propertyNodes = {};
1300
+ const dependencyGroups = {
1301
+ injected: readExistingDependencyGroupProperty(depsObject, 'injected', warnings, propertyNodes),
1302
+ importDeps: readExistingDependencyGroupProperty(depsObject, 'importDeps', warnings, propertyNodes),
1303
+ providers: readExistingDependencyGroupProperty(depsObject, 'providers', warnings, propertyNodes),
1304
+ };
1305
+ return {
1306
+ found: true,
1307
+ warnings,
1308
+ dependencyGroups,
1309
+ exportAssignmentNode: defaultExport,
1310
+ depsObjectNode: depsObject,
1311
+ propertyNodes,
1312
+ };
1313
+ }
1314
+ export function formatDependencyGroups(dependencyGroups) {
1315
+ return [
1316
+ `injected=[${dependencyGroups.injected.join(', ')}]`,
1317
+ `importDeps=[${dependencyGroups.importDeps.join(', ')}]`,
1318
+ `providers=[${dependencyGroups.providers.join(', ')}]`,
1319
+ ].join(' ');
1320
+ }
1321
+ export async function runAngularBrandCodemod(options = {}) {
1322
+ const rootDir = resolve(options.rootDir ?? process.cwd());
1323
+ const config = options.config ??
1324
+ (options.configFilePath
1325
+ ? loadAngularBrandConfigFromFile(options.configFilePath)
1326
+ : loadDiscoveredAngularBrandConfig(rootDir));
1327
+ const tsConfigFilePath = options.tsConfigFilePath
1328
+ ? resolve(options.tsConfigFilePath)
1329
+ : getDefaultTsConfigPath(rootDir);
1330
+ const project = createProject(tsConfigFilePath);
1331
+ const files = collectTypeScriptFiles(rootDir);
1332
+ project.addSourceFilesAtPaths(files);
1333
+ setProjectQuoteKind(project);
1334
+ const summary = {
1335
+ transformedFiles: 0,
1336
+ skippedFiles: 0,
1337
+ warnings: 0,
1338
+ countByAngularKind: {
1339
+ component: 0,
1340
+ directive: 0,
1341
+ injectable: 0,
1342
+ pipe: 0,
1343
+ },
1344
+ files: [],
1345
+ };
1346
+ for (const sourceFile of project.getSourceFiles()) {
1347
+ if (!isWithinRoot(sourceFile.getFilePath(), rootDir)) {
1348
+ continue;
1349
+ }
1350
+ const result = transformSourceFile(sourceFile, {
1351
+ ...options,
1352
+ config,
1353
+ });
1354
+ const report = {
1355
+ ...result,
1356
+ filePath: sourceFile.getFilePath(),
1357
+ };
1358
+ summary.files.push(report);
1359
+ summary.warnings += result.warnings.length;
1360
+ if (result.changed) {
1361
+ summary.transformedFiles += 1;
1362
+ if (result.angularKind &&
1363
+ result.angularKind in summary.countByAngularKind) {
1364
+ summary.countByAngularKind[result.angularKind] += 1;
1365
+ }
1366
+ if (!options.dryRun) {
1367
+ await sourceFile.save();
1368
+ }
1369
+ }
1370
+ if (result.skipped) {
1371
+ summary.skippedFiles += 1;
1372
+ }
1373
+ logFileResult(report, options.log ?? console.log);
1374
+ }
1375
+ logSummary(summary, options.log ?? console.log);
1376
+ return summary;
1377
+ }
1378
+ function normalizeOptions(options) {
1379
+ return {
1380
+ helperImportPath: options.helperImportPath ?? DEFAULT_OPTIONS.helperImportPath,
1381
+ transformOnlyStandaloneDeclarables: options.transformOnlyStandaloneDeclarables ??
1382
+ DEFAULT_OPTIONS.transformOnlyStandaloneDeclarables,
1383
+ includeProviders: options.includeProviders ?? DEFAULT_OPTIONS.includeProviders,
1384
+ includeViewProviders: options.includeViewProviders ?? DEFAULT_OPTIONS.includeViewProviders,
1385
+ config: options.config,
1386
+ configFilePath: options.configFilePath,
1387
+ };
1388
+ }
1389
+ function skip(result, warnings) {
1390
+ result.skipped = true;
1391
+ result.warnings.push(...warnings);
1392
+ return result;
1393
+ }
1394
+ function getDecoratorName(decorator) {
1395
+ const expression = decorator.getExpression();
1396
+ if (Node.isCallExpression(expression)) {
1397
+ const callTarget = expression.getExpression();
1398
+ if (Node.isIdentifier(callTarget)) {
1399
+ return callTarget.getText();
1400
+ }
1401
+ if (Node.isPropertyAccessExpression(callTarget)) {
1402
+ return callTarget.getName();
1403
+ }
1404
+ return undefined;
1405
+ }
1406
+ if (Node.isIdentifier(expression)) {
1407
+ return expression.getText();
1408
+ }
1409
+ if (Node.isPropertyAccessExpression(expression)) {
1410
+ return expression.getName();
1411
+ }
1412
+ return undefined;
1413
+ }
1414
+ function decoratorLabel(angularKind) {
1415
+ switch (angularKind) {
1416
+ case 'component':
1417
+ return '@Component';
1418
+ case 'directive':
1419
+ return '@Directive';
1420
+ case 'pipe':
1421
+ return '@Pipe';
1422
+ case 'injectable':
1423
+ return '@Injectable';
1424
+ }
1425
+ }
1426
+ function getDecoratorMetadataObject(classDeclaration) {
1427
+ for (const decorator of classDeclaration.getDecorators()) {
1428
+ if (!getAngularKindFromDecorator(decorator)) {
1429
+ continue;
1430
+ }
1431
+ const callExpression = decorator.getCallExpression();
1432
+ const [metadata] = callExpression?.getArguments() ?? [];
1433
+ if (Node.isObjectLiteralExpression(metadata)) {
1434
+ return metadata;
1435
+ }
1436
+ }
1437
+ return undefined;
1438
+ }
1439
+ function getAngularKindFromDecorator(decorator) {
1440
+ const decoratorName = getDecoratorName(decorator);
1441
+ return decoratorName && decoratorName in SUPPORTED_DECORATORS
1442
+ ? SUPPORTED_DECORATORS[decoratorName]
1443
+ : undefined;
1444
+ }
1445
+ function getObjectPropertyAssignment(objectLiteral, propertyName) {
1446
+ const property = objectLiteral.getProperties().find((objectProperty) => {
1447
+ if (!Node.isPropertyAssignment(objectProperty)) {
1448
+ return false;
1449
+ }
1450
+ return getStaticPropertyName(objectProperty.getNameNode()) === propertyName;
1451
+ });
1452
+ return Node.isPropertyAssignment(property) ? property : undefined;
1453
+ }
1454
+ function extractDecoratorMetadataDepGroups(classDeclaration, angularKind, options) {
1455
+ const sourceFile = classDeclaration.getSourceFile();
1456
+ const metadata = getDecoratorMetadataObject(classDeclaration);
1457
+ const groups = emptyMetadataDependencyGroups();
1458
+ if (!metadata) {
1459
+ return groups;
1460
+ }
1461
+ groups.imports = extractMetadataArrayProperty(metadata, 'imports', 'imports', groups.warnings);
1462
+ groups.hostDirectives = extractMetadataArrayProperty(metadata, 'hostDirectives', 'hostDirectives', groups.warnings);
1463
+ if (options.includeProviders) {
1464
+ groups.providers = extractMetadataArrayProperty(metadata, 'providers', 'providers', groups.warnings);
1465
+ }
1466
+ if (options.includeViewProviders && angularKind === 'component') {
1467
+ groups.viewProviders = extractMetadataArrayProperty(metadata, 'viewProviders', 'viewProviders', groups.warnings);
1468
+ }
1469
+ groups.occurrences = [
1470
+ ...groups.imports.map((dependencyText) => createMetadataDependencyOccurrence(sourceFile, dependencyText, 'imports')),
1471
+ ...groups.hostDirectives.map((dependencyText) => createMetadataDependencyOccurrence(sourceFile, dependencyText, 'hostDirectives')),
1472
+ ];
1473
+ return groups;
1474
+ }
1475
+ function emptyMetadataDependencyGroups() {
1476
+ return {
1477
+ imports: [],
1478
+ hostDirectives: [],
1479
+ providers: [],
1480
+ viewProviders: [],
1481
+ occurrences: [],
1482
+ warnings: [],
1483
+ };
1484
+ }
1485
+ function createMetadataDependencyOccurrence(sourceFile, dependencyText, metadataContext) {
1486
+ const resolution = resolveDependencyReference(sourceFile, dependencyText);
1487
+ return {
1488
+ dependencyText,
1489
+ symbolName: resolution.importedName ??
1490
+ dependencyText.split('.').pop() ??
1491
+ dependencyText,
1492
+ moduleSpecifier: resolution.moduleSpecifier,
1493
+ metadataContext,
1494
+ };
1495
+ }
1496
+ function extractProvidedDependencies(sourceFile, classDeclaration, angularKind, options) {
1497
+ const metadata = getDecoratorMetadataObject(classDeclaration);
1498
+ const warnings = [];
1499
+ const entries = [];
1500
+ if (!metadata) {
1501
+ return { entries, warnings };
1502
+ }
1503
+ if (options.includeProviders) {
1504
+ entries.push(...extractProvidedDependencyArrayProperty(sourceFile, metadata, 'providers', warnings));
1505
+ }
1506
+ if (options.includeViewProviders && angularKind === 'component') {
1507
+ entries.push(...extractProvidedDependencyArrayProperty(sourceFile, metadata, 'viewProviders', warnings));
1508
+ }
1509
+ return {
1510
+ entries: mergeGeneratedDependencyEntries(entries),
1511
+ warnings,
1512
+ };
1513
+ }
1514
+ function extractProvidedDependencyArrayProperty(sourceFile, metadata, propertyName, warnings) {
1515
+ const property = getObjectPropertyAssignment(metadata, propertyName);
1516
+ if (!property) {
1517
+ return [];
1518
+ }
1519
+ const initializer = property.getInitializer();
1520
+ if (!Node.isArrayLiteralExpression(initializer)) {
1521
+ warnings.push(`Skipped metadata property "${propertyName}" because it is not a static array.`);
1522
+ return [];
1523
+ }
1524
+ return extractProvidedDependencyEntriesFromArray(sourceFile, initializer, propertyName, warnings);
1525
+ }
1526
+ function extractProvidedDependencyEntriesFromArray(sourceFile, arrayLiteral, context, warnings) {
1527
+ const entries = [];
1528
+ for (const element of arrayLiteral.getElements()) {
1529
+ if (Node.isArrayLiteralExpression(element)) {
1530
+ entries.push(...extractProvidedDependencyEntriesFromArray(sourceFile, element, context, warnings));
1531
+ continue;
1532
+ }
1533
+ if (Node.isSpreadElement(element)) {
1534
+ warnings.push(`Skipped spread element in "${context}" metadata providers.`);
1535
+ continue;
1536
+ }
1537
+ if (Node.isCallExpression(element)) {
1538
+ const providerFactory = getStaticExpressionText(element.getExpression());
1539
+ if (!providerFactory) {
1540
+ warnings.push(`Skipped complex provider factory "${element.getText()}" in "${context}".`);
1541
+ continue;
1542
+ }
1543
+ entries.push({
1544
+ key: createGeneratedDependencyKey(providerFactory),
1545
+ typeText: `ReturnType<typeof ${providerFactory}>`,
1546
+ });
1547
+ continue;
1548
+ }
1549
+ const staticExpression = getStaticExpressionText(element);
1550
+ if (staticExpression) {
1551
+ entries.push({
1552
+ key: createGeneratedDependencyKey(staticExpression),
1553
+ typeText: createGeneratedDependencyTypeText(sourceFile, staticExpression),
1554
+ });
1555
+ continue;
1556
+ }
1557
+ if (Node.isObjectLiteralExpression(element)) {
1558
+ const providerEntry = extractProvidedDependencyEntryFromObjectLiteral(sourceFile, element, context, warnings);
1559
+ if (providerEntry) {
1560
+ entries.push(providerEntry);
1561
+ }
1562
+ continue;
1563
+ }
1564
+ warnings.push(`Skipped complex expression "${element.getText()}" in "${context}" metadata providers.`);
1565
+ }
1566
+ return entries;
1567
+ }
1568
+ function extractProvidedDependencyEntryFromObjectLiteral(sourceFile, objectLiteral, context, warnings) {
1569
+ const provideProperty = getObjectPropertyAssignment(objectLiteral, 'provide');
1570
+ const provideText = getStaticExpressionText(provideProperty?.getInitializer());
1571
+ if (!provideText) {
1572
+ warnings.push(`Skipped provider object in "${context}" because "provide" is not static.`);
1573
+ return undefined;
1574
+ }
1575
+ const useClass = getStaticExpressionText(getObjectPropertyAssignment(objectLiteral, 'useClass')?.getInitializer());
1576
+ const useExisting = getStaticExpressionText(getObjectPropertyAssignment(objectLiteral, 'useExisting')?.getInitializer());
1577
+ const useValue = getStaticExpressionText(getObjectPropertyAssignment(objectLiteral, 'useValue')?.getInitializer());
1578
+ const useFactory = getStaticExpressionText(getObjectPropertyAssignment(objectLiteral, 'useFactory')?.getInitializer());
1579
+ if (useFactory) {
1580
+ return {
1581
+ key: createGeneratedDependencyKey(provideText),
1582
+ typeText: `ReturnType<typeof ${useFactory}>`,
1583
+ };
1584
+ }
1585
+ if (useValue) {
1586
+ return {
1587
+ key: createGeneratedDependencyKey(provideText),
1588
+ typeText: `typeof ${useValue}`,
1589
+ };
1590
+ }
1591
+ const valueText = useClass ?? useExisting ?? provideText;
1592
+ return {
1593
+ key: createGeneratedDependencyKey(provideText),
1594
+ typeText: createGeneratedDependencyTypeText(sourceFile, valueText),
1595
+ };
1596
+ }
1597
+ function extractMetadataArrayProperty(metadata, propertyName, context, warnings) {
1598
+ const property = getObjectPropertyAssignment(metadata, propertyName);
1599
+ if (!property) {
1600
+ return [];
1601
+ }
1602
+ const initializer = property.getInitializer();
1603
+ if (!Node.isArrayLiteralExpression(initializer)) {
1604
+ warnings.push(`Skipped metadata property "${propertyName}" because it is not a static array.`);
1605
+ return [];
1606
+ }
1607
+ return extractStaticArrayElements(initializer, context, warnings);
1608
+ }
1609
+ function extractStaticArrayElements(arrayLiteral, context, warnings) {
1610
+ const dependencies = [];
1611
+ for (const element of arrayLiteral.getElements()) {
1612
+ if (Node.isArrayLiteralExpression(element)) {
1613
+ dependencies.push(...extractStaticArrayElements(element, context, warnings));
1614
+ continue;
1615
+ }
1616
+ if (Node.isSpreadElement(element)) {
1617
+ warnings.push(`Skipped spread element in "${context}" metadata dependencies.`);
1618
+ continue;
1619
+ }
1620
+ if (isProviderContext(context) && Node.isCallExpression(element)) {
1621
+ const providerFactory = getStaticExpressionText(element.getExpression());
1622
+ if (providerFactory) {
1623
+ dependencies.push(providerFactory);
1624
+ continue;
1625
+ }
1626
+ }
1627
+ const dependency = getStaticExpressionText(element);
1628
+ if (dependency) {
1629
+ dependencies.push(dependency);
1630
+ continue;
1631
+ }
1632
+ if (Node.isObjectLiteralExpression(element)) {
1633
+ dependencies.push(...extractStaticObjectElementDependencies(element, context, warnings));
1634
+ continue;
1635
+ }
1636
+ warnings.push(`Skipped complex expression "${element.getText()}" in "${context}" metadata dependencies.`);
1637
+ }
1638
+ return dependencies;
1639
+ }
1640
+ function isProviderContext(context) {
1641
+ return context === 'providers' || context === 'viewProviders';
1642
+ }
1643
+ function extractStaticObjectElementDependencies(objectLiteral, context, warnings) {
1644
+ const dependencies = [];
1645
+ const knownValueProperties = context === 'hostDirectives'
1646
+ ? new Set(['directive'])
1647
+ : new Set(['provide', 'useClass', 'useExisting']);
1648
+ for (const property of objectLiteral.getProperties()) {
1649
+ if (!Node.isPropertyAssignment(property)) {
1650
+ warnings.push(`Skipped complex object member in "${context}" metadata dependencies.`);
1651
+ continue;
1652
+ }
1653
+ const propertyName = getStaticPropertyName(property.getNameNode());
1654
+ if (!propertyName || !knownValueProperties.has(propertyName)) {
1655
+ continue;
1656
+ }
1657
+ const dependency = getStaticExpressionText(property.getInitializer());
1658
+ if (dependency) {
1659
+ dependencies.push(dependency);
1660
+ continue;
1661
+ }
1662
+ warnings.push(`Skipped complex "${propertyName}" value in "${context}" metadata dependencies.`);
1663
+ }
1664
+ return dependencies;
1665
+ }
1666
+ function getInjectDecoratorToken(decorators, warnings) {
1667
+ for (const decorator of decorators) {
1668
+ if (getDecoratorName(decorator) !== 'Inject') {
1669
+ continue;
1670
+ }
1671
+ const [token] = decorator.getCallExpression()?.getArguments() ?? [];
1672
+ const dependency = getStaticExpressionText(token);
1673
+ if (!dependency) {
1674
+ warnings.push('Skipped @Inject() dependency because the token is not a static identifier.');
1675
+ return { found: true };
1676
+ }
1677
+ return { found: true, dependency };
1678
+ }
1679
+ return { found: false };
1680
+ }
1681
+ function getStaticExpressionText(expression) {
1682
+ if (!expression) {
1683
+ return undefined;
1684
+ }
1685
+ if (Node.isIdentifier(expression)) {
1686
+ return expression.getText();
1687
+ }
1688
+ if (Node.isPropertyAccessExpression(expression)) {
1689
+ return expression.getText();
1690
+ }
1691
+ return undefined;
1692
+ }
1693
+ function getStaticPropertyName(nameNode) {
1694
+ if (Node.isIdentifier(nameNode)) {
1695
+ return nameNode.getText();
1696
+ }
1697
+ if (Node.isStringLiteral(nameNode) || Node.isNumericLiteral(nameNode)) {
1698
+ return nameNode.getLiteralText();
1699
+ }
1700
+ return undefined;
1701
+ }
1702
+ function resolveDependencyReference(sourceFile, dependencyText) {
1703
+ const [rootIdentifier] = dependencyText.split('.');
1704
+ if (!rootIdentifier) {
1705
+ return { kind: 'unknown' };
1706
+ }
1707
+ const importResolution = resolveImportedDependencyReference(sourceFile, dependencyText, rootIdentifier);
1708
+ if (importResolution) {
1709
+ return importResolution;
1710
+ }
1711
+ const localClass = sourceFile.getClass(rootIdentifier);
1712
+ if (localClass) {
1713
+ return { kind: 'class', classDeclaration: localClass };
1714
+ }
1715
+ if (sourceFile.getEnum(rootIdentifier)) {
1716
+ return { kind: 'enum' };
1717
+ }
1718
+ if (sourceFile.getFunction(rootIdentifier)) {
1719
+ return { kind: 'function' };
1720
+ }
1721
+ if (sourceFile
1722
+ .getVariableDeclarations()
1723
+ .some((declaration) => declaration.getName() === rootIdentifier)) {
1724
+ return { kind: 'variable' };
1725
+ }
1726
+ return { kind: 'unknown' };
1727
+ }
1728
+ function resolveImportedDependencyReference(sourceFile, dependencyText, rootIdentifier) {
1729
+ for (const importDeclaration of sourceFile.getImportDeclarations()) {
1730
+ const moduleSpecifier = importDeclaration.getModuleSpecifierValue();
1731
+ if (importDeclaration.getDefaultImport()?.getText() === rootIdentifier) {
1732
+ const declaration = getDefaultImportClassDeclaration(importDeclaration);
1733
+ if (declaration) {
1734
+ return {
1735
+ kind: 'class',
1736
+ classDeclaration: declaration,
1737
+ importedName: rootIdentifier,
1738
+ moduleSpecifier,
1739
+ };
1740
+ }
1741
+ return { kind: 'unknown', importedName: rootIdentifier, moduleSpecifier };
1742
+ }
1743
+ if (importDeclaration.getNamespaceImport()?.getText() === rootIdentifier) {
1744
+ const declaration = getNamespaceImportClassDeclaration(importDeclaration, dependencyText);
1745
+ if (declaration) {
1746
+ return {
1747
+ kind: 'class',
1748
+ classDeclaration: declaration,
1749
+ importedName: dependencyText.split('.').pop(),
1750
+ moduleSpecifier,
1751
+ };
1752
+ }
1753
+ return {
1754
+ kind: 'namespace',
1755
+ importedName: dependencyText.split('.').pop(),
1756
+ moduleSpecifier,
1757
+ };
1758
+ }
1759
+ const namedImport = importDeclaration
1760
+ .getNamedImports()
1761
+ .find((specifier) => {
1762
+ const localName = specifier.getAliasNode()?.getText() ??
1763
+ specifier.getNameNode().getText();
1764
+ return localName === rootIdentifier;
1765
+ });
1766
+ if (!namedImport) {
1767
+ continue;
1768
+ }
1769
+ const localIdentifier = namedImport.getAliasNode() ?? namedImport.getNameNode();
1770
+ const symbol = localIdentifier.getSymbol() ?? localIdentifier.getType().getSymbol();
1771
+ const aliasedSymbol = symbol?.getAliasedSymbol() ?? symbol;
1772
+ const declarations = aliasedSymbol?.getDeclarations() ?? [];
1773
+ const classDeclaration = declarations.find((declaration) => Node.isClassDeclaration(declaration));
1774
+ if (classDeclaration && Node.isClassDeclaration(classDeclaration)) {
1775
+ return {
1776
+ kind: 'class',
1777
+ classDeclaration,
1778
+ importedName: namedImport.getNameNode().getText(),
1779
+ moduleSpecifier,
1780
+ };
1781
+ }
1782
+ if (declarations.some((declaration) => Node.isEnumDeclaration(declaration))) {
1783
+ return {
1784
+ kind: 'enum',
1785
+ importedName: namedImport.getNameNode().getText(),
1786
+ moduleSpecifier,
1787
+ };
1788
+ }
1789
+ if (declarations.some((declaration) => Node.isFunctionDeclaration(declaration))) {
1790
+ return {
1791
+ kind: 'function',
1792
+ importedName: namedImport.getNameNode().getText(),
1793
+ moduleSpecifier,
1794
+ };
1795
+ }
1796
+ if (declarations.some((declaration) => Node.isVariableDeclaration(declaration))) {
1797
+ return {
1798
+ kind: 'variable',
1799
+ importedName: namedImport.getNameNode().getText(),
1800
+ moduleSpecifier,
1801
+ };
1802
+ }
1803
+ return {
1804
+ kind: 'unknown',
1805
+ importedName: namedImport.getNameNode().getText(),
1806
+ moduleSpecifier,
1807
+ };
1808
+ }
1809
+ return undefined;
1810
+ }
1811
+ function getDefaultImportClassDeclaration(importDeclaration) {
1812
+ const moduleSourceFile = importDeclaration.getModuleSpecifierSourceFile();
1813
+ if (!moduleSourceFile) {
1814
+ return undefined;
1815
+ }
1816
+ const defaultExportSymbol = moduleSourceFile.getDefaultExportSymbol();
1817
+ const declarations = defaultExportSymbol?.getDeclarations() ?? [];
1818
+ return declarations.find((declaration) => Node.isClassDeclaration(declaration));
1819
+ }
1820
+ function getNamespaceImportClassDeclaration(importDeclaration, dependencyText) {
1821
+ const moduleSourceFile = importDeclaration.getModuleSpecifierSourceFile();
1822
+ if (!moduleSourceFile) {
1823
+ return undefined;
1824
+ }
1825
+ const leafIdentifier = dependencyText.split('.').pop();
1826
+ if (!leafIdentifier) {
1827
+ return undefined;
1828
+ }
1829
+ const exportedDeclarations = moduleSourceFile.getExportedDeclarations().get(leafIdentifier) ?? [];
1830
+ return exportedDeclarations.find((declaration) => Node.isClassDeclaration(declaration));
1831
+ }
1832
+ function isProvidedInInjectableTree(sourceFile, dependencyText) {
1833
+ const resolution = resolveDependencyReference(sourceFile, dependencyText);
1834
+ const classDeclaration = resolution.classDeclaration;
1835
+ if (!classDeclaration || getAngularKind(classDeclaration) !== 'injectable') {
1836
+ return false;
1837
+ }
1838
+ const metadata = getDecoratorMetadataObject(classDeclaration);
1839
+ const providedInProperty = metadata
1840
+ ? getObjectPropertyAssignment(metadata, 'providedIn')
1841
+ : undefined;
1842
+ const initializer = providedInProperty?.getInitializer();
1843
+ if (!initializer) {
1844
+ return false;
1845
+ }
1846
+ return (!Node.isNullLiteral(initializer) &&
1847
+ !Node.isFalseLiteral(initializer) &&
1848
+ !(Node.isIdentifier(initializer) && initializer.getText() === 'undefined'));
1849
+ }
1850
+ function shouldGenerateLocalMissingProvider(sourceFile, dependencyText) {
1851
+ return (!resolveTrackedInjectHelperByName(sourceFile, dependencyText) &&
1852
+ !isProvidedInInjectableTree(sourceFile, dependencyText));
1853
+ }
1854
+ function resolveTrackedInjectHelperByName(sourceFile, dependencyText) {
1855
+ const [rootIdentifier] = dependencyText.split('.');
1856
+ if (!rootIdentifier) {
1857
+ return undefined;
1858
+ }
1859
+ for (const declaration of resolveDependencyIdentifierDeclarations(sourceFile, rootIdentifier)) {
1860
+ const helper = resolveTrackedInjectHelperFromDeclaration(declaration);
1861
+ if (helper) {
1862
+ return helper;
1863
+ }
1864
+ }
1865
+ return undefined;
1866
+ }
1867
+ function resolveDependencyIdentifierDeclarations(sourceFile, rootIdentifier) {
1868
+ for (const importDeclaration of sourceFile.getImportDeclarations()) {
1869
+ const namedImport = importDeclaration
1870
+ .getNamedImports()
1871
+ .find((specifier) => {
1872
+ const localName = specifier.getAliasNode()?.getText() ??
1873
+ specifier.getNameNode().getText();
1874
+ return localName === rootIdentifier;
1875
+ });
1876
+ if (!namedImport) {
1877
+ continue;
1878
+ }
1879
+ const identifier = namedImport.getAliasNode() ?? namedImport.getNameNode();
1880
+ const symbol = identifier.getSymbol() ?? identifier.getType().getSymbol();
1881
+ const aliasedSymbol = symbol?.getAliasedSymbol() ?? symbol;
1882
+ return aliasedSymbol?.getDeclarations() ?? [namedImport];
1883
+ }
1884
+ return sourceFile
1885
+ .getDescendantsOfKind(SyntaxKind.BindingElement)
1886
+ .filter((bindingElement) => bindingElement.getName() === rootIdentifier);
1887
+ }
1888
+ function getDependencyTextFromTypeNode(typeNode) {
1889
+ if (isPrimitiveTypeNode(typeNode)) {
1890
+ return undefined;
1891
+ }
1892
+ if (Node.isTypeReference(typeNode)) {
1893
+ const typeName = typeNode.getTypeName();
1894
+ const dependency = typeName.getText();
1895
+ return isPrimitiveDependencyText(dependency) ? undefined : dependency;
1896
+ }
1897
+ if (Node.isExpressionWithTypeArguments(typeNode)) {
1898
+ const expression = typeNode.getExpression();
1899
+ return getStaticExpressionText(expression);
1900
+ }
1901
+ return undefined;
1902
+ }
1903
+ function isPrimitiveTypeNode(typeNode) {
1904
+ return [
1905
+ SyntaxKind.AnyKeyword,
1906
+ SyntaxKind.BigIntKeyword,
1907
+ SyntaxKind.BooleanKeyword,
1908
+ SyntaxKind.FalseKeyword,
1909
+ SyntaxKind.NeverKeyword,
1910
+ SyntaxKind.NullKeyword,
1911
+ SyntaxKind.NumberKeyword,
1912
+ SyntaxKind.ObjectKeyword,
1913
+ SyntaxKind.StringKeyword,
1914
+ SyntaxKind.SymbolKeyword,
1915
+ SyntaxKind.TrueKeyword,
1916
+ SyntaxKind.UndefinedKeyword,
1917
+ SyntaxKind.UnknownKeyword,
1918
+ SyntaxKind.VoidKeyword,
1919
+ ].includes(typeNode.getKind());
1920
+ }
1921
+ function isPrimitiveDependencyText(dependency) {
1922
+ return PRIMITIVE_TYPE_TEXTS.has(dependency);
1923
+ }
1924
+ function isRuntimeSafeTypeDependency(typeNode, dependency) {
1925
+ const identifiers = getDependencyIdentifiersFromTypeNode(typeNode);
1926
+ if (identifiers.length === 0 ||
1927
+ identifiers.map((identifier) => identifier.getText()).join('.') !==
1928
+ dependency) {
1929
+ return false;
1930
+ }
1931
+ const [rootIdentifier] = identifiers;
1932
+ const leafIdentifier = identifiers[identifiers.length - 1];
1933
+ const isQualifiedDependency = identifiers.length > 1;
1934
+ return (isRuntimeSafeIdentifier(rootIdentifier, {
1935
+ allowNamespaceContainer: isQualifiedDependency,
1936
+ }) &&
1937
+ isRuntimeSafeIdentifier(leafIdentifier, { allowNamespaceContainer: false }));
1938
+ }
1939
+ function getDependencyIdentifiersFromTypeNode(typeNode) {
1940
+ if (!Node.isTypeReference(typeNode)) {
1941
+ return [];
1942
+ }
1943
+ const typeName = typeNode.getTypeName();
1944
+ if (Node.isIdentifier(typeName)) {
1945
+ return [typeName];
1946
+ }
1947
+ return typeName.getDescendantsOfKind(SyntaxKind.Identifier);
1948
+ }
1949
+ function isRuntimeSafeIdentifier(identifier, options) {
1950
+ const symbol = identifier.getSymbol() ?? identifier.getType().getSymbol();
1951
+ if (!symbol) {
1952
+ return false;
1953
+ }
1954
+ const declarations = symbol.getDeclarations();
1955
+ if (declarations.length === 0) {
1956
+ return false;
1957
+ }
1958
+ if (declarations.some((declaration) => isTypeOnlyImportDeclaration(declaration))) {
1959
+ return false;
1960
+ }
1961
+ if (options.allowNamespaceContainer &&
1962
+ declarations.some((declaration) => Node.isNamespaceImport(declaration))) {
1963
+ return true;
1964
+ }
1965
+ const aliasedSymbol = symbol.getAliasedSymbol();
1966
+ const runtimeDeclarations = aliasedSymbol?.getDeclarations() ?? declarations;
1967
+ if (runtimeDeclarations.length === 0) {
1968
+ return false;
1969
+ }
1970
+ return runtimeDeclarations.some((declaration) => isRuntimeValueDeclaration(declaration, options));
1971
+ }
1972
+ function isTypeOnlyImportDeclaration(declaration) {
1973
+ if (Node.isImportSpecifier(declaration)) {
1974
+ return (declaration.isTypeOnly() ||
1975
+ declaration.getImportDeclaration().isTypeOnly());
1976
+ }
1977
+ if (Node.isImportClause(declaration)) {
1978
+ return declaration.isTypeOnly();
1979
+ }
1980
+ if (Node.isNamespaceImport(declaration)) {
1981
+ return (declaration
1982
+ .getFirstAncestorByKind(SyntaxKind.ImportDeclaration)
1983
+ ?.isTypeOnly() ?? false);
1984
+ }
1985
+ return false;
1986
+ }
1987
+ function isRuntimeValueDeclaration(declaration, options) {
1988
+ if (Node.isClassDeclaration(declaration) ||
1989
+ Node.isEnumDeclaration(declaration) ||
1990
+ Node.isFunctionDeclaration(declaration) ||
1991
+ Node.isVariableDeclaration(declaration)) {
1992
+ return true;
1993
+ }
1994
+ if (options.allowNamespaceContainer &&
1995
+ Node.isModuleDeclaration(declaration)) {
1996
+ return true;
1997
+ }
1998
+ return false;
1999
+ }
2000
+ function getExportRewriteSafety(sourceFile, classDeclaration, className) {
2001
+ const warnings = [];
2002
+ for (const statement of sourceFile.getStatements()) {
2003
+ if (Node.isFunctionDeclaration(statement) && statement.isDefaultExport()) {
2004
+ warnings.push('Skipped file because it has an unrelated default-exported function.');
2005
+ }
2006
+ if (Node.isClassDeclaration(statement) &&
2007
+ statement !== classDeclaration &&
2008
+ statement.isDefaultExport()) {
2009
+ warnings.push('Skipped file because it has an unrelated default-exported class.');
2010
+ }
2011
+ }
2012
+ for (const exportAssignment of sourceFile.getExportAssignments()) {
2013
+ if (exportAssignment.isExportEquals()) {
2014
+ warnings.push('Skipped file because export = is not safe to combine with export default.');
2015
+ continue;
2016
+ }
2017
+ if (!isDefaultIdentifierExport(exportAssignment, className) &&
2018
+ !isDefaultBrandedExport(exportAssignment, className)) {
2019
+ warnings.push(`Skipped file because it has a complex default export: ${exportAssignment.getText()}`);
2020
+ }
2021
+ }
2022
+ for (const exportDeclaration of sourceFile.getExportDeclarations()) {
2023
+ const defaultExportSpecifier = exportDeclaration
2024
+ .getNamedExports()
2025
+ .find((specifier) => {
2026
+ const alias = specifier.getAliasNode()?.getText();
2027
+ return (alias === 'default' || (!alias && specifier.getName() === 'default'));
2028
+ });
2029
+ if (defaultExportSpecifier &&
2030
+ defaultExportSpecifier.getName() !== className) {
2031
+ warnings.push('Skipped file because it has an unrelated named default export.');
2032
+ }
2033
+ if (exportDeclaration.getModuleSpecifier()) {
2034
+ const reExportsClass = exportDeclaration
2035
+ .getNamedExports()
2036
+ .some((specifier) => specifier.getName() === className);
2037
+ if (reExportsClass) {
2038
+ warnings.push(`Skipped file because it re-exports "${className}" from another module.`);
2039
+ }
2040
+ }
2041
+ }
2042
+ return warnings.length === 0
2043
+ ? { safe: true, warnings: [] }
2044
+ : { safe: false, warnings };
2045
+ }
2046
+ function getHelperImportSafety(sourceFile, helperImportPath) {
2047
+ const warnings = [
2048
+ 'DerivedService',
2049
+ 'GetDeps',
2050
+ 'GetServiceDependencies',
2051
+ 'GetPublicComponentProperties',
2052
+ 'GetServiceOutput',
2053
+ ]
2054
+ .filter((helperName) => hasConflictingTopLevelBinding(sourceFile, helperImportPath, helperName))
2055
+ .map((helperName) => `Skipped file because "${helperName}" is already bound to a different top-level symbol.`);
2056
+ return warnings.length === 0
2057
+ ? { safe: true, warnings: [] }
2058
+ : { safe: false, warnings };
2059
+ }
2060
+ function hasConflictingTopLevelBinding(sourceFile, helperImportPath, helperName) {
2061
+ for (const importDeclaration of sourceFile.getImportDeclarations()) {
2062
+ if (importDeclaration.getDefaultImport()?.getText() === helperName) {
2063
+ return true;
2064
+ }
2065
+ if (importDeclaration.getNamespaceImport()?.getText() === helperName) {
2066
+ return true;
2067
+ }
2068
+ for (const namedImport of importDeclaration.getNamedImports()) {
2069
+ const localName = namedImport.getAliasNode()?.getText() ?? namedImport.getName();
2070
+ if (localName !== helperName) {
2071
+ continue;
2072
+ }
2073
+ return importDeclaration.getModuleSpecifierValue() !== helperImportPath;
2074
+ }
2075
+ }
2076
+ for (const statement of sourceFile.getStatements()) {
2077
+ if (Node.isClassDeclaration(statement) &&
2078
+ statement.getName() === helperName) {
2079
+ return true;
2080
+ }
2081
+ if (Node.isFunctionDeclaration(statement) &&
2082
+ statement.getName() === helperName) {
2083
+ return true;
2084
+ }
2085
+ if (Node.isEnumDeclaration(statement) &&
2086
+ statement.getName() === helperName) {
2087
+ return true;
2088
+ }
2089
+ if (Node.isInterfaceDeclaration(statement) &&
2090
+ statement.getName() === helperName) {
2091
+ return true;
2092
+ }
2093
+ if (Node.isTypeAliasDeclaration(statement) &&
2094
+ statement.getName() === helperName) {
2095
+ return true;
2096
+ }
2097
+ if (Node.isVariableStatement(statement)) {
2098
+ const hasVariableName = statement
2099
+ .getDeclarationList()
2100
+ .getDeclarations()
2101
+ .some((declaration) => declaration.getName() === helperName);
2102
+ if (hasVariableName) {
2103
+ return true;
2104
+ }
2105
+ }
2106
+ }
2107
+ return false;
2108
+ }
2109
+ function isDefaultIdentifierExport(exportAssignment, className) {
2110
+ if (exportAssignment.isExportEquals()) {
2111
+ return false;
2112
+ }
2113
+ const expression = exportAssignment.getExpression();
2114
+ return Node.isIdentifier(expression) && expression.getText() === className;
2115
+ }
2116
+ function isDefaultBrandedExport(exportAssignment, className) {
2117
+ if (exportAssignment.isExportEquals()) {
2118
+ return false;
2119
+ }
2120
+ const expression = exportAssignment.getExpression();
2121
+ if (!Node.isCallExpression(expression)) {
2122
+ return false;
2123
+ }
2124
+ const callTarget = expression.getExpression();
2125
+ if (!Node.isIdentifier(callTarget) ||
2126
+ callTarget.getText() !== 'brandAngularSymbol') {
2127
+ return false;
2128
+ }
2129
+ const [targetClass] = expression.getArguments();
2130
+ return Node.isIdentifier(targetClass) && targetClass.getText() === className;
2131
+ }
2132
+ function removeClassFromExportDeclaration(exportDeclaration, className) {
2133
+ if (exportDeclaration.getModuleSpecifier()) {
2134
+ return;
2135
+ }
2136
+ for (const namedExport of exportDeclaration.getNamedExports()) {
2137
+ if (namedExport.getName() === className) {
2138
+ namedExport.remove();
2139
+ }
2140
+ }
2141
+ if (exportDeclaration.getNamedExports().length === 0) {
2142
+ exportDeclaration.remove();
2143
+ }
2144
+ }
2145
+ function hasLocalNamedImport(sourceFile, helperImportPath, importName) {
2146
+ return sourceFile.getImportDeclarations().some((importDeclaration) => {
2147
+ if (importDeclaration.getModuleSpecifierValue() !== helperImportPath) {
2148
+ return false;
2149
+ }
2150
+ return importDeclaration.getNamedImports().some((namedImport) => {
2151
+ const localName = namedImport.getAliasNode()?.getText() ?? namedImport.getName();
2152
+ return localName === importName;
2153
+ });
2154
+ });
2155
+ }
2156
+ function createProject(tsConfigFilePath) {
2157
+ if (tsConfigFilePath) {
2158
+ return new Project({
2159
+ skipAddingFilesFromTsConfig: true,
2160
+ tsConfigFilePath,
2161
+ });
2162
+ }
2163
+ return new Project({
2164
+ compilerOptions: {
2165
+ experimentalDecorators: true,
2166
+ module: ts.ModuleKind.Preserve,
2167
+ moduleResolution: ts.ModuleResolutionKind.Node10,
2168
+ target: ts.ScriptTarget.ES2022,
2169
+ },
2170
+ });
2171
+ }
2172
+ function getDefaultTsConfigPath(rootDir) {
2173
+ const tsConfigPath = join(rootDir, 'tsconfig.json');
2174
+ return existsSync(tsConfigPath) ? tsConfigPath : undefined;
2175
+ }
2176
+ function collectTypeScriptFiles(rootDir) {
2177
+ const files = [];
2178
+ collectTypeScriptFilesInto(rootDir, files);
2179
+ return files;
2180
+ }
2181
+ function collectTypeScriptFilesInto(directory, files) {
2182
+ for (const entryName of readdirSync(directory)) {
2183
+ const entryPath = join(directory, entryName);
2184
+ const stats = statSync(entryPath);
2185
+ if (stats.isDirectory()) {
2186
+ if (!IGNORED_DIRECTORIES.has(entryName)) {
2187
+ collectTypeScriptFilesInto(entryPath, files);
2188
+ }
2189
+ continue;
2190
+ }
2191
+ if (!stats.isFile() ||
2192
+ extname(entryName) !== '.ts' ||
2193
+ isGeneratedFile(entryPath)) {
2194
+ continue;
2195
+ }
2196
+ files.push(entryPath);
2197
+ }
2198
+ }
2199
+ function isGeneratedFile(filePath) {
2200
+ const fileName = basename(filePath);
2201
+ return (GENERATED_FILE_SUFFIXES.some((suffix) => fileName.endsWith(suffix)) ||
2202
+ filePath.includes('/generated/') ||
2203
+ filePath.includes('\\generated\\'));
2204
+ }
2205
+ function setProjectQuoteKind(project) {
2206
+ const firstModuleSpecifier = project
2207
+ .getSourceFiles()
2208
+ .flatMap((sourceFile) => sourceFile.getImportDeclarations())
2209
+ .map((importDeclaration) => importDeclaration.getModuleSpecifier().getText())
2210
+ .find(Boolean);
2211
+ project.manipulationSettings.set({
2212
+ quoteKind: firstModuleSpecifier?.startsWith('"')
2213
+ ? QuoteKind.Double
2214
+ : QuoteKind.Single,
2215
+ });
2216
+ }
2217
+ function isWithinRoot(filePath, rootDir) {
2218
+ const absoluteFilePath = isAbsolute(filePath) ? filePath : resolve(filePath);
2219
+ return (absoluteFilePath === rootDir || absoluteFilePath.startsWith(`${rootDir}/`));
2220
+ }
2221
+ function logFileResult(report, log) {
2222
+ if (!report.changed && !report.skipped && !report.angularKind) {
2223
+ return;
2224
+ }
2225
+ const status = report.changed
2226
+ ? 'transformed'
2227
+ : report.skipped
2228
+ ? 'skipped'
2229
+ : 'unchanged';
2230
+ const details = [
2231
+ `file=${report.filePath}`,
2232
+ report.angularKind ? `kind=${report.angularKind}` : undefined,
2233
+ report.className ? `class=${report.className}` : undefined,
2234
+ `deps=[${report.generatedDependencyGroups.deps.map((entry) => entry.key).join(', ')}]`,
2235
+ `provided=[${report.generatedDependencyGroups.provided.map((entry) => entry.key).join(', ')}]`,
2236
+ `missingProvider=[${report.generatedDependencyGroups.missingProvider.map((entry) => entry.key).join(', ')}]`,
2237
+ `status=${status}`,
2238
+ ].filter(Boolean);
2239
+ log(details.join(' '));
2240
+ for (const warning of report.warnings) {
2241
+ log(` warning: ${warning}`);
2242
+ }
2243
+ }
2244
+ function logSummary(summary, log) {
2245
+ log([
2246
+ `summary transformed=${summary.transformedFiles}`,
2247
+ `skipped=${summary.skippedFiles}`,
2248
+ `warnings=${summary.warnings}`,
2249
+ `components=${summary.countByAngularKind.component}`,
2250
+ `directives=${summary.countByAngularKind.directive}`,
2251
+ `pipes=${summary.countByAngularKind.pipe}`,
2252
+ `injectables=${summary.countByAngularKind.injectable}`,
2253
+ ].join(' '));
2254
+ }
2255
+ function parseCliArgs(argv) {
2256
+ const options = {};
2257
+ for (let index = 0; index < argv.length; index += 1) {
2258
+ const arg = argv[index];
2259
+ switch (arg) {
2260
+ case '--root':
2261
+ options.rootDir = argv[++index];
2262
+ break;
2263
+ case '--tsconfig':
2264
+ options.tsConfigFilePath = argv[++index];
2265
+ break;
2266
+ case '--config':
2267
+ options.configFilePath = argv[++index];
2268
+ break;
2269
+ case '--helper-import':
2270
+ options.helperImportPath = argv[++index];
2271
+ break;
2272
+ case '--transform-only-standalone-declarables':
2273
+ options.transformOnlyStandaloneDeclarables = true;
2274
+ break;
2275
+ case '--no-providers':
2276
+ options.includeProviders = false;
2277
+ break;
2278
+ case '--no-view-providers':
2279
+ options.includeViewProviders = false;
2280
+ break;
2281
+ case '--dry-run':
2282
+ options.dryRun = true;
2283
+ break;
2284
+ case '--help':
2285
+ printHelpAndExit();
2286
+ break;
2287
+ default:
2288
+ throw new Error(`Unknown argument: ${arg}`);
2289
+ }
2290
+ }
2291
+ return options;
2292
+ }
2293
+ function printHelpAndExit() {
2294
+ console.log(`Usage: craft-brand [options]
2295
+
2296
+ Options:
2297
+ --root <dir> Project root. Defaults to cwd.
2298
+ --tsconfig <path> tsconfig path. Defaults to <root>/tsconfig.json.
2299
+ --config <path> Explicit angular brand config file path.
2300
+ --helper-import <path> Import path for GetDeps.
2301
+ --transform-only-standalone-declarables Only transform standalone components/directives.
2302
+ --no-providers Do not include metadata providers in generated types.
2303
+ --no-view-providers Do not include component viewProviders in generated types.
2304
+ --dry-run Print results without writing files.
2305
+ --help Show this help.
2306
+ `);
2307
+ process.exit(0);
2308
+ }
2309
+ // Check if this module is being run directly (ESM equivalent of require.main === module)
2310
+ if (import.meta.url === `file://${process.argv[1]}` ||
2311
+ (import.meta.url.startsWith('file:') &&
2312
+ process.argv[1] === fileURLToPath(import.meta.url))) {
2313
+ runAngularBrandCodemod(parseCliArgs(process.argv.slice(2))).catch((error) => {
2314
+ console.error(error);
2315
+ process.exitCode = 1;
2316
+ });
2317
+ }
2318
+ //# sourceMappingURL=angular-brand-codemod.js.map