@angular-modernizer/api 0.1.0

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 (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +507 -0
  3. package/dist/analysis/complexity-metrics-calculator.d.ts +99 -0
  4. package/dist/analysis/complexity-metrics-calculator.d.ts.map +1 -0
  5. package/dist/analysis/complexity-metrics-calculator.js +195 -0
  6. package/dist/analysis/complexity-metrics-calculator.js.map +1 -0
  7. package/dist/analysis/dependency-analyzer.d.ts +227 -0
  8. package/dist/analysis/dependency-analyzer.d.ts.map +1 -0
  9. package/dist/analysis/dependency-analyzer.js +451 -0
  10. package/dist/analysis/dependency-analyzer.js.map +1 -0
  11. package/dist/analysis/external-template-loader.d.ts +44 -0
  12. package/dist/analysis/external-template-loader.d.ts.map +1 -0
  13. package/dist/analysis/external-template-loader.js +69 -0
  14. package/dist/analysis/external-template-loader.js.map +1 -0
  15. package/dist/analysis/selector-mapper.d.ts +77 -0
  16. package/dist/analysis/selector-mapper.d.ts.map +1 -0
  17. package/dist/analysis/selector-mapper.js +146 -0
  18. package/dist/analysis/selector-mapper.js.map +1 -0
  19. package/dist/analysis/service-detector.d.ts +164 -0
  20. package/dist/analysis/service-detector.d.ts.map +1 -0
  21. package/dist/analysis/service-detector.js +231 -0
  22. package/dist/analysis/service-detector.js.map +1 -0
  23. package/dist/analysis/service-pattern-recognizer.d.ts +247 -0
  24. package/dist/analysis/service-pattern-recognizer.d.ts.map +1 -0
  25. package/dist/analysis/service-pattern-recognizer.js +444 -0
  26. package/dist/analysis/service-pattern-recognizer.js.map +1 -0
  27. package/dist/analysis/symbol-locator.d.ts +81 -0
  28. package/dist/analysis/symbol-locator.d.ts.map +1 -0
  29. package/dist/analysis/symbol-locator.js +128 -0
  30. package/dist/analysis/symbol-locator.js.map +1 -0
  31. package/dist/analysis/template-analyzer-usage.d.ts +144 -0
  32. package/dist/analysis/template-analyzer-usage.d.ts.map +1 -0
  33. package/dist/analysis/template-analyzer-usage.js +474 -0
  34. package/dist/analysis/template-analyzer-usage.js.map +1 -0
  35. package/dist/analysis/template-analyzer.d.ts +80 -0
  36. package/dist/analysis/template-analyzer.d.ts.map +1 -0
  37. package/dist/analysis/template-analyzer.js +146 -0
  38. package/dist/analysis/template-analyzer.js.map +1 -0
  39. package/dist/index.d.ts +23 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +22 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/investigation/angular-symbol-finder.d.ts +76 -0
  44. package/dist/investigation/angular-symbol-finder.d.ts.map +1 -0
  45. package/dist/investigation/angular-symbol-finder.js +199 -0
  46. package/dist/investigation/angular-symbol-finder.js.map +1 -0
  47. package/dist/investigation/call-graph-builder.d.ts +85 -0
  48. package/dist/investigation/call-graph-builder.d.ts.map +1 -0
  49. package/dist/investigation/call-graph-builder.js +196 -0
  50. package/dist/investigation/call-graph-builder.js.map +1 -0
  51. package/dist/investigation/codebase-searcher.d.ts +99 -0
  52. package/dist/investigation/codebase-searcher.d.ts.map +1 -0
  53. package/dist/investigation/codebase-searcher.js +201 -0
  54. package/dist/investigation/codebase-searcher.js.map +1 -0
  55. package/dist/investigation/stack-trace-parser.d.ts +70 -0
  56. package/dist/investigation/stack-trace-parser.d.ts.map +1 -0
  57. package/dist/investigation/stack-trace-parser.js +117 -0
  58. package/dist/investigation/stack-trace-parser.js.map +1 -0
  59. package/dist/investigation/type-resolver.d.ts +59 -0
  60. package/dist/investigation/type-resolver.d.ts.map +1 -0
  61. package/dist/investigation/type-resolver.js +111 -0
  62. package/dist/investigation/type-resolver.js.map +1 -0
  63. package/dist/investigation/usage-finder.d.ts +69 -0
  64. package/dist/investigation/usage-finder.d.ts.map +1 -0
  65. package/dist/investigation/usage-finder.js +132 -0
  66. package/dist/investigation/usage-finder.js.map +1 -0
  67. package/dist/metrics/complexity-metrics.d.ts +196 -0
  68. package/dist/metrics/complexity-metrics.d.ts.map +1 -0
  69. package/dist/metrics/complexity-metrics.js +445 -0
  70. package/dist/metrics/complexity-metrics.js.map +1 -0
  71. package/dist/public-api.d.ts +77 -0
  72. package/dist/public-api.d.ts.map +1 -0
  73. package/dist/public-api.js +50 -0
  74. package/dist/public-api.js.map +1 -0
  75. package/dist/transformation/import-manager.d.ts +276 -0
  76. package/dist/transformation/import-manager.d.ts.map +1 -0
  77. package/dist/transformation/import-manager.js +488 -0
  78. package/dist/transformation/import-manager.js.map +1 -0
  79. package/dist/transformation/ng-morph-adapter.d.ts +111 -0
  80. package/dist/transformation/ng-morph-adapter.d.ts.map +1 -0
  81. package/dist/transformation/ng-morph-adapter.js +179 -0
  82. package/dist/transformation/ng-morph-adapter.js.map +1 -0
  83. package/dist/transformation/ngmodule-manager.d.ts +145 -0
  84. package/dist/transformation/ngmodule-manager.d.ts.map +1 -0
  85. package/dist/transformation/ngmodule-manager.js +356 -0
  86. package/dist/transformation/ngmodule-manager.js.map +1 -0
  87. package/package.json +62 -0
  88. package/src/analysis/complexity-metrics-calculator.ts +253 -0
  89. package/src/analysis/dependency-analyzer.ts +720 -0
  90. package/src/analysis/external-template-loader.ts +79 -0
  91. package/src/analysis/selector-mapper.ts +186 -0
  92. package/src/analysis/service-detector.ts +280 -0
  93. package/src/analysis/service-pattern-recognizer.ts +545 -0
  94. package/src/analysis/symbol-locator.ts +170 -0
  95. package/src/analysis/template-analyzer-usage.ts +614 -0
  96. package/src/analysis/template-analyzer.ts +208 -0
  97. package/src/index.ts +51 -0
  98. package/src/investigation/angular-symbol-finder.ts +263 -0
  99. package/src/investigation/call-graph-builder.ts +298 -0
  100. package/src/investigation/codebase-searcher.ts +286 -0
  101. package/src/investigation/stack-trace-parser.ts +166 -0
  102. package/src/investigation/type-resolver.ts +144 -0
  103. package/src/investigation/usage-finder.ts +198 -0
  104. package/src/metrics/complexity-metrics.ts +617 -0
  105. package/src/public-api.ts +119 -0
  106. package/src/transformation/import-manager.ts +626 -0
  107. package/src/transformation/ng-morph-adapter.ts +252 -0
  108. package/src/transformation/ngmodule-manager.ts +450 -0
@@ -0,0 +1,208 @@
1
+ /**
2
+ * @angular-modernizer/api - Template Analyzer
3
+ *
4
+ * Dumb Collector - extracts ALL elements from Angular templates.
5
+ * Zero filtering, zero intelligence. Based on the "Trust the Map" principle.
6
+ *
7
+ * Philosophy: "Collect Everything, Filter Later"
8
+ * - No business logic in collectors
9
+ * - Plugins decide what matters
10
+ * - Raw data is more valuable than pre-filtered data
11
+ */
12
+
13
+ /**
14
+ * Represents a single template usage (element, directive, pipe, etc.).
15
+ */
16
+ export interface TemplateUsage {
17
+ /**
18
+ * Type of usage.
19
+ */
20
+ type:
21
+ | 'element'
22
+ | 'directive'
23
+ | 'pipe'
24
+ | 'component'
25
+ | 'structural-directive'
26
+ | 'control-flow';
27
+
28
+ /**
29
+ * Name/selector of the element.
30
+ */
31
+ name: string;
32
+
33
+ /**
34
+ * Position in template (line number).
35
+ */
36
+ line?: number;
37
+
38
+ /**
39
+ * Position in template (column number).
40
+ */
41
+ column?: number;
42
+
43
+ /**
44
+ * Raw HTML/template string of this usage.
45
+ */
46
+ raw?: string;
47
+
48
+ /**
49
+ * Attributes/inputs on this element.
50
+ */
51
+ attributes?: Record<string, string>;
52
+
53
+ /**
54
+ * Additional metadata.
55
+ */
56
+ metadata?: Record<string, unknown>;
57
+ }
58
+
59
+ /**
60
+ * Template Analyzer - extracts all usages from Angular templates.
61
+ *
62
+ * This is a "Dumb Collector" that doesn't filter or interpret.
63
+ * It just collects everything and lets plugins decide what's important.
64
+ */
65
+ export class TemplateAnalyzer {
66
+ /**
67
+ * Analyze a template string and extract all usages.
68
+ *
69
+ * @param template - The template string to analyze
70
+ * @returns Array of template usages
71
+ *
72
+ * Philosophy: "Collect Everything, Filter Later"
73
+ * - Extracts ALL elements, attributes, pipes, directives
74
+ * - No filtering, no business logic
75
+ * - Plugins decide what's important using the SymbolMapper
76
+ */
77
+ analyze(template: string): TemplateUsage[] {
78
+ const usages: TemplateUsage[] = [];
79
+ const lines = template.split('\n');
80
+
81
+ // Extract element tags (e.g., <fixture-child>, <div>, <custom-component>)
82
+ const elementRegex = /<([a-zA-Z][\w-]*)/g;
83
+
84
+ // Extract pipes (e.g., {{ value | pipeName }}, {{ value | pipeName: arg }})
85
+ const pipeRegex = /\|\s*([a-zA-Z]\w*)/g;
86
+
87
+ lines.forEach((line, lineIndex) => {
88
+ // Extract elements
89
+ let match: RegExpExecArray | null;
90
+ while ((match = elementRegex.exec(line)) !== null) {
91
+ const tagName = match[1];
92
+ if (tagName) {
93
+ usages.push({
94
+ type: 'element',
95
+ name: tagName,
96
+ line: lineIndex + 1,
97
+ column: match.index,
98
+ raw: match[0],
99
+ });
100
+ }
101
+ }
102
+
103
+ // Extract attributes - use multiple patterns for better coverage
104
+
105
+ // Pattern 1: Structural directives (*ngIf, *ngFor)
106
+ const structuralDirectiveRegex = /\*([a-zA-Z][\w-]*)/g;
107
+ while ((match = structuralDirectiveRegex.exec(line)) !== null) {
108
+ usages.push({
109
+ type: 'structural-directive',
110
+ name: `*${match[1]}`,
111
+ line: lineIndex + 1,
112
+ column: match.index,
113
+ raw: match[0],
114
+ });
115
+ }
116
+
117
+ // Pattern 2: Angular 17+ control flow (@if, @for, @switch, @empty, @let)
118
+ const controlFlowRegex =
119
+ /@(if|else if|for|switch|case|default|empty|let)\b/g;
120
+ while ((match = controlFlowRegex.exec(line)) !== null) {
121
+ usages.push({
122
+ type: 'control-flow',
123
+ name: match[1]!,
124
+ line: lineIndex + 1,
125
+ column: match.index,
126
+ raw: match[0],
127
+ });
128
+ }
129
+
130
+ // Pattern 3: Property bindings ([propName]="value")
131
+ const propertyBindingRegex = /\[([a-zA-Z][\w-]*)\]/g;
132
+ while ((match = propertyBindingRegex.exec(line)) !== null) {
133
+ usages.push({
134
+ type: 'directive',
135
+ name: `[${match[1]}]`,
136
+ line: lineIndex + 1,
137
+ column: match.index,
138
+ raw: match[0],
139
+ });
140
+ }
141
+
142
+ // Pattern 3: Bare attribute selectors (fixtureAttribute, ngModel)
143
+ // Look for attribute names that are NOT standard HTML attributes
144
+ const bareAttributeRegex =
145
+ /\s+([a-z][\w-]*(?:[A-Z][\w-]*)+)\s*(?:=|>|\s|$)/g;
146
+ while ((match = bareAttributeRegex.exec(line)) !== null) {
147
+ const attrName = match[1];
148
+ // Only add if it looks like a directive (has camelCase or kebab-case)
149
+ if (attrName && (attrName.includes('-') || /[A-Z]/.test(attrName))) {
150
+ usages.push({
151
+ type: 'directive',
152
+ name: attrName,
153
+ line: lineIndex + 1,
154
+ column: match.index,
155
+ raw: match[0].trim(),
156
+ });
157
+ }
158
+ }
159
+
160
+ // Extract pipes
161
+ const pipeMatches = line.matchAll(pipeRegex);
162
+ for (const pipeMatch of pipeMatches) {
163
+ const pipeName = pipeMatch[1];
164
+ if (pipeName) {
165
+ usages.push({
166
+ type: 'pipe',
167
+ name: pipeName,
168
+ line: lineIndex + 1,
169
+ column: pipeMatch.index,
170
+ raw: pipeMatch[0],
171
+ });
172
+ }
173
+ }
174
+ });
175
+
176
+ return usages;
177
+ }
178
+
179
+ /**
180
+ * Extract inline template from component decorator.
181
+ *
182
+ * @param decoratorText - The `` decorator text
183
+ * @returns The template string or undefined
184
+ */
185
+ extractInlineTemplate(decoratorText: string): string | undefined {
186
+ // Match template: `...` or template: '...' (including multiline)
187
+ const templateRegex = /template\s*:\s*(['`])([\s\S]*?)\1/;
188
+ const match = templateRegex.exec(decoratorText);
189
+
190
+ if (match) {
191
+ return match[2];
192
+ }
193
+
194
+ return undefined;
195
+ }
196
+
197
+ /**
198
+ * Check if a template uses a specific selector.
199
+ *
200
+ * @param template - The template string
201
+ * @param selector - The selector to search for
202
+ * @returns True if selector is used
203
+ */
204
+ usesSelector(template: string, selector: string): boolean {
205
+ const usages = this.analyze(template);
206
+ return usages.some((usage) => usage.name === selector);
207
+ }
208
+ }
package/src/index.ts ADDED
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Main entry point for @angular-modernizer/api
3
+ */
4
+
5
+ export * from './public-api.js';
6
+ export * from './transformation/ng-morph-adapter.js';
7
+ export * from './transformation/ngmodule-manager.js';
8
+ export * from './transformation/import-manager.js';
9
+ export * from './analysis/service-pattern-recognizer.js';
10
+ export * from './analysis/service-detector.js';
11
+ export type { ServiceDetectionResult } from './analysis/service-pattern-recognizer.js';
12
+ export * from './analysis/dependency-analyzer.js';
13
+ export * from './analysis/template-analyzer.js';
14
+ export * from './analysis/template-analyzer-usage.js';
15
+ export * from './analysis/selector-mapper.js';
16
+ export * from './analysis/external-template-loader.js';
17
+ export {
18
+ SymbolLocator,
19
+ type SymbolInfo as ProjectSymbolInfo,
20
+ } from './analysis/symbol-locator.js';
21
+ export {
22
+ StackTraceParser,
23
+ type StackFrame,
24
+ } from './investigation/stack-trace-parser.js';
25
+ export {
26
+ UsageFinder,
27
+ type SymbolUsage,
28
+ type UsageType,
29
+ type FindUsagesOptions,
30
+ } from './investigation/usage-finder.js';
31
+ export {
32
+ CallGraphBuilder,
33
+ type CallNode,
34
+ type CallGraphOptions,
35
+ type CallGraphResult,
36
+ } from './investigation/call-graph-builder.js';
37
+ export {
38
+ TypeResolver,
39
+ type ResolvedTypeInfo,
40
+ } from './investigation/type-resolver.js';
41
+ export {
42
+ AngularSymbolFinder,
43
+ type AngularSymbolResult,
44
+ type AngularSymbolType,
45
+ } from './investigation/angular-symbol-finder.js';
46
+ export {
47
+ CodebaseSearcher,
48
+ type SearchResult,
49
+ type SearchOptions,
50
+ type SymbolKind,
51
+ } from './investigation/codebase-searcher.js';
@@ -0,0 +1,263 @@
1
+ /**
2
+ * @angular-modernizer/api - Angular Symbol Finder
3
+ *
4
+ * Finds Angular symbols (components, directives, pipes, services, guards,
5
+ * interceptors, resolvers, modules, tokens) by selector, template name, class
6
+ * name, or token name. Supports standard Angular decorators as well as
7
+ * ngUWI-specific external decorators (@ExportComponent, @ExportFunction).
8
+ *
9
+ * @remarks
10
+ * The project must already have the relevant source files added before calling
11
+ * `findSymbol`. Queries are matched case-sensitively for class names, and
12
+ * substring-matched for selectors/pipe names (e.g. searching "app-list" also
13
+ * returns "app-list-item" selectors).
14
+ *
15
+ * @example
16
+ * ```typescript
17
+ * const finder = new AngularSymbolFinder(project);
18
+ * const results = finder.findSymbol('app-user');
19
+ * // results[0] → { file, className, angularType: 'component', selector: 'app-user', metadata: {...} }
20
+ *
21
+ * const pipes = finder.findSymbol('dateFormat', 'pipe');
22
+ * // pipes[0] → { file, className: 'DateFormatPipe', angularType: 'pipe', pipeName: 'dateFormat', metadata: {...} }
23
+ * ```
24
+ */
25
+
26
+ import type { Project, ClassDeclaration, Decorator } from 'ts-morph';
27
+
28
+ /** The Angular symbol type classification. */
29
+ export type AngularSymbolType =
30
+ | 'component'
31
+ | 'directive'
32
+ | 'pipe'
33
+ | 'service'
34
+ | 'guard'
35
+ | 'interceptor'
36
+ | 'resolver'
37
+ | 'module'
38
+ | 'token';
39
+
40
+ /** A resolved Angular symbol. */
41
+ export interface AngularSymbolResult {
42
+ /** Absolute file path containing the class. */
43
+ file: string;
44
+
45
+ /** Class name (e.g. `UserComponent`). */
46
+ className: string;
47
+
48
+ /** Angular symbol type. */
49
+ angularType: AngularSymbolType;
50
+
51
+ /** CSS selector for components and directives (stripped of brackets). */
52
+ selector?: string;
53
+
54
+ /** Template name for pipes (value of `@Pipe({ name: '...' })`). */
55
+ pipeName?: string;
56
+
57
+ /** Raw decorator metadata (key-value pairs extracted from the decorator). */
58
+ metadata: Record<string, string>;
59
+ }
60
+
61
+ /** Decorator names that map to Angular symbol types. */
62
+ const DECORATOR_TYPE_MAP: Record<string, AngularSymbolType> = {
63
+ Component: 'component',
64
+ ExportComponent: 'component',
65
+ Directive: 'directive',
66
+ Pipe: 'pipe',
67
+ Injectable: 'service',
68
+ ExportFunction: 'service',
69
+ NgModule: 'module',
70
+ Guard: 'guard',
71
+ Interceptor: 'interceptor',
72
+ Resolver: 'resolver',
73
+ };
74
+
75
+ /**
76
+ * Extracts the string value of a named property from a decorator's argument
77
+ * object literal (e.g. `selector: 'app-foo'` → `'app-foo'`).
78
+ */
79
+ function extractDecoratorProperty(
80
+ decorator: Decorator,
81
+ property: string,
82
+ ): string | undefined {
83
+ try {
84
+ const args = decorator.getArguments();
85
+ if (args.length === 0) {
86
+ return undefined;
87
+ }
88
+ const arg = args[0];
89
+ if (arg === undefined) {
90
+ return undefined;
91
+ }
92
+ const text = arg.getText();
93
+ const match = new RegExp(`${property}\\s*:\\s*['"\`]([^'"\`]+)['"\`]`).exec(
94
+ text,
95
+ );
96
+ return match?.[1];
97
+ } catch {
98
+ return undefined;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Extracts all string-valued properties from a decorator's first argument
104
+ * as a flat `Record<string, string>`.
105
+ */
106
+ function extractDecoratorMetadata(
107
+ decorator: Decorator,
108
+ ): Record<string, string> {
109
+ const metadata: Record<string, string> = {};
110
+ try {
111
+ const args = decorator.getArguments();
112
+ const firstArg = args[0];
113
+ if (args.length === 0 || firstArg === undefined) {
114
+ return metadata;
115
+ }
116
+ const text = firstArg.getText();
117
+ const pattern = /(\w+)\s*:\s*['"`]([^'"`]+)['"`]/g;
118
+ let match: RegExpExecArray | null;
119
+ while ((match = pattern.exec(text)) !== null) {
120
+ const key = match[1];
121
+ const value = match[2];
122
+ if (key !== undefined && value !== undefined) {
123
+ metadata[key] = value;
124
+ }
125
+ }
126
+ } catch {
127
+ // ignore
128
+ }
129
+ return metadata;
130
+ }
131
+
132
+ /**
133
+ * Strips attribute-selector brackets from a selector string.
134
+ * `[appHighlight]` → `appHighlight`; `app-user` → `app-user`.
135
+ */
136
+ function normalizeSelector(raw: string): string {
137
+ return raw.replace(/^\[(.+)\]$/, '$1');
138
+ }
139
+
140
+ /**
141
+ * Returns `true` when the query matches the given selector (substring) or
142
+ * class name (exact).
143
+ */
144
+ function matchesQuery(
145
+ query: string,
146
+ className: string,
147
+ selector: string | undefined,
148
+ pipeName: string | undefined,
149
+ ): boolean {
150
+ if (className === query) {
151
+ return true;
152
+ }
153
+ if (selector?.includes(query)) {
154
+ return true;
155
+ }
156
+ if (pipeName !== undefined && pipeName === query) {
157
+ return true;
158
+ }
159
+ return false;
160
+ }
161
+
162
+ /**
163
+ * Finds Angular symbols across all source files in a ts-morph `Project`.
164
+ */
165
+ export class AngularSymbolFinder {
166
+ constructor(private readonly project: Project) {}
167
+
168
+ /**
169
+ * Find Angular symbols matching `query`.
170
+ *
171
+ * @param query - Selector (e.g. `app-user`, `appHighlight`), pipe name
172
+ * (e.g. `dateFormat`), class name (e.g. `UserComponent`), or partial
173
+ * selector substring.
174
+ * @param type - Optional filter; when provided only results of the given type
175
+ * are returned.
176
+ * @returns Array of matching `AngularSymbolResult` objects, empty when none
177
+ * are found.
178
+ *
179
+ * @example
180
+ * ```typescript
181
+ * // By selector
182
+ * finder.findSymbol('app-user');
183
+ *
184
+ * // By class name with type filter
185
+ * finder.findSymbol('UserService', 'service');
186
+ * ```
187
+ */
188
+ findSymbol(query: string, type?: AngularSymbolType): AngularSymbolResult[] {
189
+ const results: AngularSymbolResult[] = [];
190
+
191
+ for (const sourceFile of this.project.getSourceFiles()) {
192
+ for (const cls of sourceFile.getClasses()) {
193
+ const result = this.classifyClass(cls);
194
+ if (result === null) {
195
+ continue;
196
+ }
197
+ if (type !== undefined && result.angularType !== type) {
198
+ continue;
199
+ }
200
+ if (
201
+ matchesQuery(
202
+ query,
203
+ result.className,
204
+ result.selector,
205
+ result.pipeName,
206
+ )
207
+ ) {
208
+ results.push(result);
209
+ }
210
+ }
211
+ }
212
+
213
+ return results;
214
+ }
215
+
216
+ /**
217
+ * Attempts to classify a class declaration as an Angular symbol.
218
+ * Returns `null` when the class carries no recognised Angular decorator.
219
+ */
220
+ private classifyClass(cls: ClassDeclaration): AngularSymbolResult | null {
221
+ for (const decorator of cls.getDecorators()) {
222
+ const decoratorName = decorator.getName();
223
+ const angularType = DECORATOR_TYPE_MAP[decoratorName];
224
+ if (angularType === undefined) {
225
+ continue;
226
+ }
227
+
228
+ const className = cls.getName() ?? '(anonymous)';
229
+ const file = cls.getSourceFile().getFilePath();
230
+ const metadata = extractDecoratorMetadata(decorator);
231
+
232
+ let selector: string | undefined;
233
+ let pipeName: string | undefined;
234
+
235
+ if (angularType === 'component' || angularType === 'directive') {
236
+ const rawSelector = extractDecoratorProperty(decorator, 'selector');
237
+ if (rawSelector !== undefined) {
238
+ selector = normalizeSelector(rawSelector);
239
+ }
240
+ }
241
+
242
+ if (angularType === 'pipe') {
243
+ pipeName = extractDecoratorProperty(decorator, 'name');
244
+ }
245
+
246
+ const result: AngularSymbolResult = {
247
+ file,
248
+ className,
249
+ angularType,
250
+ metadata,
251
+ };
252
+ if (selector !== undefined) {
253
+ result.selector = selector;
254
+ }
255
+ if (pipeName !== undefined) {
256
+ result.pipeName = pipeName;
257
+ }
258
+ return result;
259
+ }
260
+
261
+ return null;
262
+ }
263
+ }