@ngcompass/rules 0.1.1-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs ADDED
@@ -0,0 +1,432 @@
1
+ 'use strict';var ast=require('@ngcompass/ast'),engine=require('@ngcompass/engine'),Mr=require('typescript'),common=require('@ngcompass/common'),Pn=require('fs/promises'),In=require('path');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var Mr__default=/*#__PURE__*/_interopDefault(Mr);var Pn__default=/*#__PURE__*/_interopDefault(Pn);var In__default=/*#__PURE__*/_interopDefault(In);var nt=(t=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(t,{get:(e,r)=>(typeof require<"u"?require:e)[r]}):t)(function(t){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+t+'" is not supported')});var it;function u(t){let e=t;for(;e;){let r=e.type;if(r==="ChainExpression"||r==="TSNonNullExpression"||r==="TSAsExpression"||r==="ParenthesizedExpression"||r==="TSInstantiationExpression"||r==="TSSatisfiesExpression"){e=e.expression;continue}break}return e??null}function b(t){let e=t?.type;return e==="MemberExpression"||e==="StaticMemberExpression"||e==="OptionalMemberExpression"}function A(t){if(!t)return "";let e=t.property;return e?t.computed?e.type==="Literal"&&typeof e.value=="string"?e.value:"":e.name??"":""}function v(t){return t?t.start??t.span?.start??0:0}function O(t,e){let r=u(t);return !!r&&(r.type==="Identifier"?(r.name??"")===e:!!b(r)&&A(r)===e)}function B(t){let e=u(t);if(!e||e.type!=="CallExpression")return "";let r=u(e.callee);return r?.type==="Identifier"?r.name??"":b(r)?A(r):""}function ee(t){let e=u(t?.callee);return !!b(e)&&A(e)==="subscribe"}function J(t,e){if(!t||t.type!=="ObjectExpression")return null;let r=t.properties;if(!Array.isArray(r))return null;for(let n of r){if(!n||n.type!=="Property")continue;let i=n.key;if((i?.type==="Identifier"?i.name:i?.type==="Literal"&&typeof i.value=="string"?i.value:"")===e)return n}return null}function ke(t){let e=u(t);return !!(e&&e.type==="Literal"&&e.value===true)}function je(t){let e=u(t);return !e||e.type==="Literal"&&e.value===null||e.type==="Identifier"&&e.name==="undefined"}var ir=new Set(["parent","span","loc","range","start","end","type"]);function*w(t){if(t&&typeof t=="object"){if(Array.isArray(t)){for(let e of t)e&&typeof e=="object"&&(yield e);return}for(let e in t){if(ir.has(e))continue;let r=t[e];if(r)if(Array.isArray(r))for(let n of r)n&&typeof n=="object"&&(yield n);else typeof r=="object"&&r.type&&(yield r);}}}function De(t){if(!t)return false;if(t.kind==="constructor")return true;let e=t.key;return e?.type==="Identifier"&&e.name==="constructor"||e?.type==="Literal"&&e.value==="constructor"}function V(t){if(!t)return false;let e=t.type;return e==="MethodDefinition"||e==="TSAbstractMethodDefinition"||e==="ClassMethod"||e==="MethodDeclaration"}function W(t){for(let e of t)if(e&&V(e)&&De(e))return e;return null}function _(t){return t?(t.value??t)?.body??null:null}function at(t){let e=t?.key;return e?e.type==="Identifier"?e.name??"<method>":e.type==="Literal"&&typeof e.value=="string"?e.value:"<method>":"<method>"}function M(t){if(!t)return [];let e=t.body;if(Array.isArray(e))return e;if(e&&typeof e=="object"&&"body"in e){let r=e.body;if(Array.isArray(r))return r}return []}function or(t){let e=u(t);if(!e||e.type!=="CallExpression")return false;let r=e.arguments;if(!Array.isArray(r)||r.length<2)return false;let n=u(r[1]);if(!n||n.type!=="ObjectExpression")return false;let i=J(n,"injector");if(i&&!je(i.value))return true;let a=J(n,"manualCleanup");return !!(a&&ke(a.value))}function st(t){if(!t)return [];let e=[],r=[t];for(;r.length;){let n=u(r.pop());if(n)for(let i of(n.type==="CallExpression"&&O(n.callee,"effect")&&!or(n)&&e.push(n),w(n)))r.push(i);}return e}function te(t){let e=t?.arguments;if(!Array.isArray(e)||e.length===0)return null;let r=u(e[0]);return r&&(r.type==="ArrowFunctionExpression"||r.type==="FunctionExpression")?r:null}function re(t){if(!t)return null;let e=t.body;return e?u(e):null}function lt(t,e){let r=new Map([...e].map(i=>[i,new Set([i])]));if(typeof t!="string"||t.length===0)return r;let n=new Map([...e].map(i=>[i,RegExp(`^${i}(?:\\s+as\\s+([A-Za-z_$][\\w$]*))?$`)]));for(let i of [/import\s*\{\s*([^}]+)\s*\}\s*from\s*['"]rxjs['"]/g,/import\s*\{\s*([^}]+)\s*\}\s*from\s*['"]rxjs\/[^'"]+['"]/g]){let a;for(;(a=i.exec(t))!==null;)for(let l of (a[1]??"").split(",").map(s=>s.trim()).filter(Boolean))for(let[s,p]of n){let c=p.exec(l);c&&r.get(s).add(c[1]??s);}}return r}var ar=new Set(["takeUntilDestroyed","takeUntil","take","first","takeWhile"]);function sr(t){let e=u(t);if(!e)return "";if(e.type==="Identifier")return e.name??"";if(e.type==="CallExpression"){let r=u(e.callee);return r?.type==="Identifier"?r.name??"":b(r)?A(r):""}return b(e)?A(e):""}function lr(t){let e=t.arguments;if(!Array.isArray(e))return false;for(let r of e)if(ar.has(sr(r)))return true;return false}function ne(t){let e=t;for(;e;){let r=u(e);if(!r||r.type!=="CallExpression")break;let n=u(r.callee);if(!b(n))break;if(A(n)==="pipe"&&lr(r))return true;e=n?.object;}return false}function j(t,e){let r=t.template?.templateStartOffset;return typeof r=="number"&&Number.isFinite(r)?e+r:e}var ur=new Set(["get","post","put","patch","delete","head","options","jsonp","request"]),pr=new Set(["http","httpClient","_http","_httpClient"]),cr=["get","fetch","load","save","create","update","delete","remove","submit","send","post","put","patch","upload","download"];function ie(t){let e=t;for(;e;){let r=u(e);if(!r||r.type!=="CallExpression")break;let n=u(r.callee);if(!b(n))break;if(A(n)!=="pipe")return r;e=n?.object;}return null}function oe(t){if(!t)return false;let e=u(t);if(!e||e.type!=="CallExpression")return false;let r=u(e.callee);if(!b(r))return false;let n=A(r);if(!n)return false;let i=u(r?.object),a=Array.isArray(e.arguments)?e.arguments:[];if(ur.has(n)&&b(i)){let l=A(i);if(pr.has(l))return true}if(a.length>0){let l=n.toLowerCase();for(let s of cr)if(l.startsWith(s)&&n.length>s.length)return true}return false}function ae(t){if(!t)return [];let e=t.params;if(Array.isArray(e))return e;if(e&&typeof e=="object"){if("items"in e&&Array.isArray(e.items))return e.items;if("elements"in e&&Array.isArray(e.elements))return e.elements}return []}function X(t){let e=u(t);if(!e)return "";if(e.type==="Identifier")return e.name??"";if(e.type==="TSParameterProperty")return X(e.parameter);if(e.type==="AssignmentPattern"){let r=u(e.left);return r?.type==="Identifier"?r.name??"":""}if(e.type==="RestElement"){let r=u(e.argument);return r?.type==="Identifier"?r.name??"":""}return ""}function q(t){let e=u(t);if(!e)return "";if(e.type==="TSParameterProperty")return q(e.parameter);let r=e.typeAnnotation,n=u(r?.typeAnnotation??r);if(!n)return "";if(n.type==="TSTypeReference"||n.type==="TypeReference"){let i=n.typeName??n.name;if(i&&typeof i=="object"){if(i.type==="Identifier")return i.name??"";if(i.type==="TSQualifiedName")return i.right?.name??""}if(typeof i=="string")return i}return ""}var ot=false;function ut(){if(!ot){try{it=nt("typescript");}catch{}ot=true;}return it}function G(t,e){if(!t||!e.typeChecker)return;let r=ut();if(r)try{!e.sourceFile&&e.fileContent&&(e.sourceFile=r.createSourceFile(e.filePath,e.fileContent,r.ScriptTarget.Latest,!0));let n=e.sourceFile;if(!n)return;let i=(function(a,l,s){if(l<a.getStart()||l>=a.getEnd())return;let p=a;for(;;){let c=s.forEachChild(p,o=>{if(l>=o.getStart()&&l<o.getEnd())return o});if(!c)break;p=c;}return p})(n,v(t),r);return i?e.typeChecker.getSymbolAtLocation(i):void 0}catch{return}}var fr=["Service","Facade","Store","Client","Repository","Adapter","Controller","Provider","Registry","Logger","Router","Injector","Handler","Interceptor","Guard","Resolver","Validator"];function pt(t){if(!t)return false;let e=ut();if(!e)return false;try{let r=t.getName();if(fr.some(a=>r.endsWith(a)))return !0;let n=t.getDeclarations();if(!n||n.length===0)return !1;let i=n[0];if(!e.canHaveDecorators(i))return !1;for(let a of e.getDecorators(i)??[]){let l=a.expression;if(e.isCallExpression(l)&&e.isIdentifier(l.expression)&&l.expression.text==="Injectable")return !0}return !1}catch{return false}}var x={"prefer-on-push-component-change-detection":"Add `changeDetection: ChangeDetectionStrategy.OnPush` to the component metadata.","template-no-call-expression":"Move the value to a pipe, computed signal, or cached component property.","rxjs-no-subscribe-in-component":"Use `toSignal()` or the `async` pipe for view state; add `takeUntilDestroyed()` for long-lived imperative subscriptions.","template-trackby-required-for-ngfor":"Add `trackBy` to `*ngFor`, or use `track` when migrating to `@for`.","template-no-object-literal-binding":"Move the object to a component property, signal, computed value, or pure pipe.","template-no-array-literal-binding":"Move the array to a component property, signal, computed value, or pure pipe.","toSignal-require-initialValue":"Pass `{ initialValue }` or `{ requireSync: true }` so the signal has a stable type.","rxjs-avoid-subject-as-event-bus":"Use `signal()` for component UI state; move complex async pipelines into a service.","signal-no-side-effects-in-computed":"Move side effects to an `effect()` or method, and keep `computed()` pure.","signal-no-writes-in-computed":"Move `.set()` or `.update()` calls to an `effect()` or event handler.","prefer-inject-over-constructor-di":"Replace constructor parameters with field initializers that call `inject()`.","component-no-manual-detect-changes":"Use signals, `async` pipe, or input-driven state instead of calling `detectChanges()` or `markForCheck()`.","rxjs-require-takeUntilDestroyed":"Add `takeUntilDestroyed()` or another teardown operator before `subscribe()`.","template-no-async-pipe-duplication":"Store the async value once with `@if (... | async; as value)` or a view-model signal.","rxjs-prefer-toSignal-for-template-state":"Convert template-used observables to `toSignal()` and read them as signals in the template.","signal-effect-must-be-destroy-scoped":"Create the effect in an injection context or pass an explicit `{ injector }`.","signal-no-effect-in-constructor":"Move the `effect()` call to a field initializer.","signal-prefer-computed-over-sync-effect":"Replace the write-producing `effect()` with a `computed()` signal.","signal-avoid-untracked-overuse":"Remove `untracked()` unless the dependency must be intentionally ignored.","template-prefer-control-flow":"Replace the legacy directive with the matching `@if`, `@for`, or `@switch` block.","signal-prefer-input-signal":"Replace `@Input()` with `input()` or `input.required()`.","signal-prefer-output-function":"Replace `@Output() EventEmitter` with `output<T>()`.","no-bypass-sanitization":"Use Angular sanitization or a trusted server-side sanitizer instead of `bypassSecurityTrust*`.","rxjs-no-nested-subscribe":"Flatten the stream with `switchMap`, `mergeMap`, `concatMap`, or `exhaustMap`.","no-document-access":"Inject `DOCUMENT` or move browser-only DOM work into `afterNextRender()`.","template-no-unsafe-bindings":"Sanitize the value before binding, or replace the raw HTML binding with structured template content.","signal-prefer-model":"Replace the `@Input()` / `@Output()Change` pair with `model()`.","prefer-after-render-over-after-view-init":"Move browser-only DOM access into `afterNextRender()`.","spec-no-focused-test":"Replace focused or disabled test helpers with normal `describe` and `it` calls."},N={"prefer-on-push-component-change-detection":`// Before:
2
+ @Component({ selector: 'app-foo', template: '...' })
3
+ export class FooComponent { }
4
+
5
+ // After:
6
+ @Component({
7
+ selector: 'app-foo',
8
+ template: '...',
9
+ changeDetection: ChangeDetectionStrategy.OnPush,
10
+ })
11
+ export class FooComponent { }`,"rxjs-no-subscribe-in-component":`// Reactive state (data derived from a stream) \u2192 use toSignal() or async pipe:
12
+ // Before:
13
+ ngOnInit() { this.data$.subscribe(val => this.value = val); }
14
+ // After:
15
+ value = toSignal(this.data$, { initialValue: defaultVal });
16
+
17
+ // Imperative action (user-triggered, one-shot) \u2192 subscription is fine, add teardown:
18
+ // Before:
19
+ onSave() { this.api.save(this.form.value).subscribe(res => this.notify(res)); }
20
+ // After (HTTP auto-completes \u2014 takeUntilDestroyed optional but harmless):
21
+ onSave() {
22
+ this.api.save(this.form.value)
23
+ .pipe(takeUntilDestroyed(this.destroyRef))
24
+ .subscribe(res => this.notify(res));
25
+ }`,"rxjs-avoid-subject-as-event-bus":`// Case A \u2014 UI state: replace with signal()
26
+ // Before:
27
+ private loading$ = new Subject<boolean>();
28
+ // After:
29
+ loading = signal(false);
30
+
31
+ // Case B \u2014 complex async pipeline: move to a service
32
+ // Before (in component):
33
+ private search$ = new Subject<string>();
34
+ ngOnInit() { this.search$.pipe(debounceTime(300), switchMap(...)).subscribe(...); }
35
+ // After:
36
+ // SearchService owns search$ and the pipeline; component calls searchSvc.search(term)
37
+
38
+ // Note: @Input() setter bridges are intentionally exempt:
39
+ // @Input() set query(v: string) { this.search$.next(v); } \u2190 allowed`,"prefer-inject-over-constructor-di":`// Before:
40
+ constructor(private http: HttpClient, private router: Router) { }
41
+
42
+ // After:
43
+ private http = inject(HttpClient);
44
+ private router = inject(Router);`,"component-no-manual-detect-changes":`// Before:
45
+ this.cdr.detectChanges();
46
+ this.cdr.markForCheck();
47
+
48
+ // After:
49
+ // Use signals for reactive state:
50
+ count = signal(0);
51
+ // Template automatically updates when signal changes`,"rxjs-require-takeUntilDestroyed":`// Before:
52
+ this.data$.subscribe(val => this.process(val));
53
+
54
+ // After:
55
+ this.data$.pipe(
56
+ takeUntilDestroyed(this.destroyRef)
57
+ ).subscribe(val => this.process(val));`,"template-no-async-pipe-duplication":`// Before:
58
+ <div>{{ user$ | async }}</div>
59
+ <span>{{ user$ | async }}</span>
60
+
61
+ // After:
62
+ @if (user$ | async; as user) {
63
+ <div>{{ user }}</div>
64
+ <span>{{ user }}</span>
65
+ }`,"signal-no-side-effects-in-computed":`// Before:
66
+ total = computed(() => {
67
+ this.logger.log('computing'); // side effect!
68
+ return this.price() * this.qty();
69
+ });
70
+
71
+ // After:
72
+ total = computed(() => this.price() * this.qty());`,"signal-no-writes-in-computed":`// Before:
73
+ derived = computed(() => {
74
+ const val = this.source();
75
+ this.other.set(val * 2); // write inside computed!
76
+ return val;
77
+ });
78
+
79
+ // After:
80
+ derived = computed(() => this.source());
81
+ // Use effect() for the write:
82
+ syncEffect = effect(() => this.other.set(this.source() * 2));`,"signal-effect-must-be-destroy-scoped":`// Before:
83
+ ngAfterViewInit() {
84
+ effect(() => console.log(this.count())); // no injection context!
85
+ }
86
+
87
+ // After (option A - field initializer):
88
+ logEffect = effect(() => console.log(this.count()));
89
+
90
+ // After (option B - explicit injector):
91
+ ngAfterViewInit() {
92
+ effect(() => console.log(this.count()), { injector: this.injector });
93
+ }`,"signal-no-effect-in-constructor":`// Before:
94
+ constructor() {
95
+ effect(() => console.log(this.count()));
96
+ }
97
+
98
+ // After:
99
+ logEffect = effect(() => console.log(this.count()));`,"signal-prefer-computed-over-sync-effect":`// Before:
100
+ logEffect = effect(() => {
101
+ const total = this.price() * this.qty();
102
+ this.total.set(total);
103
+ });
104
+
105
+ // After:
106
+ total = computed(() => this.price() * this.qty());`,"toSignal-require-initialValue":`// Before:
107
+ data = toSignal(this.data$); // Signal<T | undefined>
108
+
109
+ // After:
110
+ data = toSignal(this.data$, { initialValue: [] }); // Signal<T>`,"template-no-call-expression":`// Before:
111
+ <div>{{ getLabel(item) }}</div>
112
+
113
+ // After (option A - pipe):
114
+ <div>{{ item | labelPipe }}</div>
115
+
116
+ // After (option B - signal):
117
+ label = computed(() => this.getLabel(this.item()));`,"template-no-object-literal-binding":`// Before:
118
+ <app-child [config]="{ color: 'red', size: 10 }"></app-child>
119
+
120
+ // After:
121
+ childConfig = signal({ color: 'red', size: 10 });
122
+ // template: <app-child [config]="childConfig()"></app-child>`,"template-no-array-literal-binding":`// Before:
123
+ <app-child [items]="[1, 2, 3]"></app-child>
124
+
125
+ // After:
126
+ items = signal([1, 2, 3]);
127
+ // template: <app-child [items]="items()"></app-child>`,"template-trackby-required-for-ngfor":`// Before:
128
+ <div *ngFor="let item of items">{{ item.name }}</div>
129
+
130
+ // After:
131
+ <div *ngFor="let item of items; trackBy: trackById">{{ item.name }}</div>
132
+
133
+ // Or with @for (Angular 17+):
134
+ @for (item of items; track item.id) {
135
+ <div>{{ item.name }}</div>
136
+ }`,"rxjs-prefer-toSignal-for-template-state":`// Before:
137
+ data$ = this.http.get('/api/data').pipe(shareReplay(1));
138
+ // template: {{ data$ | async }}
139
+
140
+ // After:
141
+ data = toSignal(this.http.get('/api/data'), { initialValue: null });
142
+ // template: {{ data() }}`,"signal-avoid-untracked-overuse":`// Acceptable:
143
+ effect(() => {
144
+ const value = this.count();
145
+ untracked(() => this.analytics.track(value));
146
+ });
147
+
148
+ // Questionable (review if untracked is needed):
149
+ const val = untracked(() => this.count());`,"template-prefer-control-flow":`// Before:
150
+ <div *ngIf="isLoggedIn">Welcome</div>
151
+ <li *ngFor="let item of items; trackBy: trackById">{{ item.name }}</li>
152
+
153
+ // After:
154
+ @if (isLoggedIn) { <div>Welcome</div> }
155
+ @for (item of items; track item.id) { <li>{{ item.name }}</li> }`,"signal-prefer-input-signal":`// Before:
156
+ @Input() title: string = '';
157
+ @Input() required count!: number;
158
+
159
+ // After:
160
+ title = input('');
161
+ count = input.required<number>();`,"signal-prefer-output-function":`// Before:
162
+ @Output() selected = new EventEmitter<Item>();
163
+
164
+ // After:
165
+ selected = output<Item>();`,"rxjs-no-nested-subscribe":`// Before:
166
+ this.user$.subscribe(user => {
167
+ this.posts$.subscribe(posts => {
168
+ this.render(user, posts);
169
+ });
170
+ });
171
+
172
+ // After:
173
+ this.user$.pipe(
174
+ switchMap(user => this.posts$.pipe(map(posts => ({ user, posts }))))
175
+ ).subscribe(({ user, posts }) => this.render(user, posts));`,"no-document-access":`// Before:
176
+ ngOnInit() {
177
+ document.title = this.title;
178
+ }
179
+
180
+ // After (inject DOCUMENT):
181
+ private doc = inject(DOCUMENT);
182
+ ngOnInit() { this.doc.title = this.title; }
183
+
184
+ // Or (SSR-safe browser-only block):
185
+ afterNextRenderEffect = afterNextRender(() => {
186
+ document.title = this.title;
187
+ });`,"signal-prefer-model":`// Before:
188
+ @Input() value: string = '';
189
+ @Output() valueChange = new EventEmitter<string>();
190
+
191
+ // After:
192
+ value = model('');
193
+ // Parent template: <app-input [(value)]="parentValue" />`,"prefer-after-render-over-after-view-init":`// Before:
194
+ ngAfterViewInit() {
195
+ this.el.nativeElement.focus();
196
+ }
197
+
198
+ // After:
199
+ constructor() {
200
+ afterNextRender(() => {
201
+ this.el.nativeElement.focus();
202
+ });
203
+ }`,"spec-no-focused-test":`// Before (focused \u2014 skips all other tests):
204
+ fdescribe('MyComponent', () => {
205
+ fit('should render', () => { ... });
206
+ });
207
+
208
+ // Before (disabled \u2014 easily forgotten):
209
+ xdescribe('MyComponent', () => {
210
+ xit('should render', () => { ... });
211
+ });
212
+
213
+ // After:
214
+ describe('MyComponent', () => {
215
+ it('should render', () => { ... });
216
+ });`};var se="component-no-manual-detect-changes",gr=new Set(["detectChanges","markForCheck"]),yr=new Set(["cdr","cdref","changedetectorref","_cdr","_cdref","changedetector","_changedetector","changedetectionref","cd","_cd"]),ft=engine.createAnyAngularClassRule(se,(t,e)=>{var r,n;let i,a,l=t.node;if(n=l,ast.analyzeComponent(n)?.type!=="Component")return null;let s=(a=(r=(function(o){let f=new Set,m=M(o),d=W(m);if(d){for(let y of ae(d.value??d))if(q(y)==="ChangeDetectorRef"){let g=X(y);g&&f.add(g);}}for(let y of m)if(y.type==="PropertyDefinition"&&y.value){let g=u(y.value);if(g&&(function(h){if(h.type!=="CallExpression")return false;let E=u(h.callee);if(E?.type!=="Identifier"||E.name!=="inject")return false;let[S]=h.arguments??[],P=u(S);return P?.type==="Identifier"&&P.name==="ChangeDetectorRef"})(g)){let h=y.key;h?.type==="Identifier"&&f.add(h.name);}}return f})(l)).size>0,o=>!!r.has(o)||!a&&yr.has(o.toLowerCase())),p=(i=ast.analyzeComponent(l),i?.type==="Component"&&i.changeDetection?.kind==="literal"&&i.changeDetection.value===ast.ChangeDetectionStrategy.OnPush),c=(function(o,f,m,d){let y=[],g=[...M(o)];for(;g.length>0;){let h=u(g.pop());if(!h)continue;let E=(function(S){if(S.type!=="CallExpression")return null;let P=u(S.callee);if(!b(P))return null;let I=A(P);return I&&gr.has(I)?I:null})(h);for(let S of(E&&(function(P,I){if(P.type!=="CallExpression")return false;let k=u(P.callee);if(!k||!b(k))return false;let T=u(k.object);if(!T)return false;if(T.type==="Identifier")return I(T.name);if(!b(T))return false;let L=u(T.object),H=A(T);return (L?.type==="ThisExpression"||L?.type==="Identifier"&&L.name==="this")&&!!H&&I(H)})(h,d)&&(m&&E==="markForCheck"||y.push((function(P,I,k,T){let L=v(P),{line:H,column:Q}=I.locator.location(L);return {filePath:I.filePath,ruleName:se,message:T?"Manual change detection in an OnPush component couples rendering to imperative calls.":`Manual change detection (${k}) can hide state-flow bugs and make rendering harder to predict.`,line:H,column:Q,severity:T?"warn":"error",fix:x[se],codeExample:N[se]}})(h,f,E,m))),w(h)))g.push(S);}return y})(l,e,p,s);return c.length>0?c:null});var Le="signal-no-side-effects-in-computed",xr=new Set(["post","put","patch","subscribe","unsubscribe","next","complete","setItem","removeItem","appendChild","removeChild","dispatch"]),br=new Set(["set","update","mutate"]),vr=new Set(["ArrowFunctionExpression","FunctionExpression","FunctionDeclaration"]),mt=engine.createCallExpressionRule(Le,(t,e)=>{if(!O(t.callee,"computed"))return null;let r=te(t),n=r?re(r):null;if(!n)return null;let i=(function(a){let l=[a];for(;l.length>0;){var s;let p=u(l.pop());if(p){if(p!==a){if((function(c){let o=u(c);if(!o)return false;if(o.type==="AssignmentExpression"){let f=u(o.left);return !!f&&b(f)}if(o.type==="UpdateExpression"||o.type==="UnaryExpression"&&o.operator==="delete"){let f=u(o.argument);return !!f&&b(f)}return false})(p))return {node:p,type:"write"};if(p.type==="CallExpression"){let c=(function(o){let f=u(o);if(!f||f.type!=="CallExpression")return "";let m=u(f.callee);return b(m)&&A(m)||""})(p);if(br.has(c))return {node:p,type:"write"};if(xr.has(c))return {node:p,type:"effect"}}}if(!(p!==a&&(s=p).type&&vr.has(s.type)))for(let c of w(p))l.push(c);}}return null})(n);return i?(function(a,l,s,p){let c=v(a)||v(l),{line:o,column:f}=s.locator.location(c);return {filePath:s.filePath,ruleName:Le,message:p==="write"?"computed() writes to state, which can create reactive cycles.":"computed() contains a side effect, so it is no longer a pure derivation.",line:o,column:f,severity:"error",fix:x[Le]}})(i.node,t,e,i.type):null});var $e="signal-effect-must-be-destroy-scoped",dt=engine.createAnyAngularClassRule($e,(t,e)=>{let r=M(t.node);if(r.length===0)return null;let n=[];for(let i of r)i&&V(i)&&!De(i)&&n.push(...(function(a,l){let s=_(a);if(!s)return [];let p=at(a),c=[];for(let o of st(s))(function(f){if(f.type!=="CallExpression")return false;let m=u(f.callee);return !!m&&(m.type==="Identifier"?m.name==="effect":!!b(m)&&A(m)==="effect")})(o)&&!(function(f){let m=u((Array.isArray(f.arguments)?f.arguments:[])[1]);if(!m||m.type!=="ObjectExpression")return false;for(let d of Array.isArray(m.properties)?m.properties:[]){let y=u(d);if(y?.type==="Property"){let g=u(y.key),h=g?.type==="Identifier"?g.name:A(g)||"";if(h==="injector")return true;if(h==="manualCleanup"){let E=u(y.value);if(E?.type==="Literal"&&E.value===true)return true}}}return false})(o)&&c.push((function(f,m,d){let y=v(f),{line:g,column:h}=d.locator.location(y);return {filePath:d.filePath,ruleName:$e,message:`effect() inside "${m}" has no explicit lifecycle owner, so cleanup can be unclear.`,line:g,column:h,severity:"error",fix:x[$e]}})(o,p,l));return c})(i,e));return n.length>0?n:null});var le="rxjs-no-nested-subscribe",Sr=new Set(["next","error","complete"]),Cr=new Set(["ArrowFunctionExpression","FunctionExpression","FunctionDeclaration"]);function Be(t){if(t.type!=="CallExpression")return false;let e=u(t.callee);return !!b(e)&&A(e)==="subscribe"}function gt(t){return !!t&&(t.type==="ArrowFunctionExpression"||t.type==="FunctionExpression")}var yt=engine.createCallExpressionRule(le,(t,e)=>{if(!Be(t))return null;for(let r of (function(n){if(!Be(n))return [];let i=Array.isArray(n.arguments)?n.arguments:[];if(i.length===0)return [];let a=u(i[0]);if(a?.type==="ObjectExpression"){if(a.type!=="ObjectExpression")return [];let s=[];for(let p of Array.isArray(a.properties)?a.properties:[]){let c=u(p);if(c?.type==="Property"&&Sr.has((function(o){let f=u(o.key);return f?f.type==="Identifier"?f.name:A(f)||"":""})(c))){let o=u(c.value);gt(o)&&s.push(o);}}return s}let l=[];for(let s=0;s<Math.min(i.length,3);s++){let p=u(i[s]);gt(p)&&l.push(p);}return l})(t)){let n=u(r.body);if(n&&(function(i){let a=[i];for(;a.length>0;){var l;let s=u(a.pop());if(s){if(s!==i&&Be(s))return true;if(!(s!==i&&(l=s).type&&Cr.has(l.type)))for(let p of w(s))a.push(p);}}return false})(n))return (function(i,a){let l=v(i),{line:s,column:p}=a.locator.location(l);return {filePath:a.filePath,ruleName:le,message:"Nested subscribe() calls make stream lifetimes harder to reason about and can leak work.",line:s,column:p,severity:"error",fix:x[le],codeExample:N[le]}})(t,e)}return null});var ue="prefer-on-push-component-change-detection";function pe(t,e){let r=t?.[e];return typeof r=="number"?r:void 0}function ce(t){return t.replace(/\\/g,"/")}var ht=engine.createComponentRule(ue,(t,e)=>{let r=t.metadata??{};return r.type!=="Component"||!(function(n){if(!n||typeof n!="object")return false;let{kind:i,value:a}=n;return i!=="non-literal"&&(i==="literal"?a!==ast.ChangeDetectionStrategy.OnPush:i==="missing")})(r.changeDetection)?null:(function(n,i){let a,l,s,p,c=(a=n.metadata??{},l=n.node,pe(a,"decoratorStart")??pe(a,"start")??pe(l,"start")??pe(n,"start")??0),{line:o,column:f}=i.locator.location(c),m=(s=n.metadata,typeof(p=s?.className)=="string"?p:"AnonymousComponent"),d=(function(y,g){if(!g.project)return null;let h=y.metadata,E=h?.className;if(typeof E!="string"||!E)return null;let S=ce(g.filePath),{ngModuleMap:P,classToFile:I}=g.project;for(let[T,L]of P){var k;if(L.isStandalone||!L.declarations.has(E))continue;let H=I.get(E);if(H&&ce(H)!==S)continue;let Q=0;for(let rt of L.declarations)rt!==E&&(k=I.get(rt))&&ce(k).endsWith(".component.ts")&&Q++;return {moduleName:(ce(T).split("/").pop()??T).replace(/\.ts$/,""),siblingComponentCount:Q}}return null})(n,i);return {filePath:i.filePath,ruleName:ue,message:(function(y,g){if(!g)return `Component '${y}' uses default change detection, which can re-render more often than needed.`;let{moduleName:h,siblingComponentCount:E}=g,S=E>0?` ${E} other component${E===1?"":"s"} are declared in '${h}'.`:"";return `Component '${y}' uses default change detection, which can re-render more often than needed.${S}`})(m,d),line:o,column:f,severity:"error",fix:x[ue],codeExample:N[ue]}})(t,e)},{requires:{projectContext:true}});var _e="template-no-call-expression",Pr=new Set(["translate","$localize","$any"]),Ir=new Set(["slice","toString","toFixed","toUpperCase","toLowerCase","trim","join","includes","indexOf","startsWith","endsWith","charAt","substring","replace","split","concat","toISOString","toLocaleDateString","toLocaleTimeString","toLocaleString"]);function xt(t){let e=u(t);return !!e&&(e.type==="CallExpression"||e.type==="OptionalCallExpression")}var bt=engine.createTemplateExpressionRule(_e,(t,e)=>(function(r,n){let i=r?[r]:[];for(;i.length>0;){let a=u(i.pop());if(a){if(xt(a)&&!(function(l){let s=u(l);if(!s||!xt(s))return false;let p=u(s.callee);if(!p)return false;if(p.type==="Identifier")return Pr.has(p.name);if(!b(p))return false;let c=A(p);return !!c&&Ir.has(c)})(a)){if((function(s){let p;return (p=u(s),Array.isArray(p?.arguments)?p.arguments:[]).length>0})(a))return true;let l=(function(s){let p=u(s);if(!p)return "";let c=u(p.callee);return c?c.type==="Identifier"?c.name??"":b(c)?A(c):"":""})(a);if(l&&!(function(s,p){let c=s.startsWith("get")||s.startsWith("is")||s.startsWith("has");if(p.crossRef?.signalMembers?.has(s))return true;if(p.typeChecker&&p.crossRef?.componentPath)try{let o=p.typeChecker,f=p.crossRef.componentPath,m=o.getProgram().getSourceFile(f);if(m){let d=m.statements.find(y=>Mr__default.default.isClassDeclaration(y));if(d&&d.name){let y=o.getTypeAtLocation(d),g=o.getPropertyOfType(y,s);if(g){if(!g)return !1;let h=g.valueDeclaration??g.declarations?.[0];if(!h)return !1;let E=o.getTypeOfSymbolAtLocation(g,h);if(!E)return !1;let S=o.typeToString(E);return S.includes("Signal")||S.includes("writable")||S.includes("computed")}}}}catch{}return !c})(l,n))return true}for(let l of w(a))i.push(l);}}return false})(t.expression,e)?[(function(r,n){let i=j(n,r.sourceSpan.start),{line:a,column:l}=n.locator.location(i);return {filePath:n.filePath,ruleName:_e,message:"Template method calls run on every change detection cycle and can make rendering slower.",line:a,column:l,severity:"error",fix:x[_e]}})(t,e)]:null,{requires:{htmlAst:true,typeChecker:true,projectContext:true}});function vt(t,e,r,n){let i=j(t,e.sourceSpan.start),{line:a,column:l}=t.locator.location(i);return {filePath:t.filePath,ruleName:r,message:n,line:a,column:l,severity:"error",fix:x[r]}}var Et=engine.createTemplateRule("template-trackby-required",(t,e)=>{let r=[];for(let n of t.attributes)n.name==="*ngFor"&&!(function(i){return !!i.match(/\btrackBy\s*:\s*([^;]+?)\s*(?:;|$)/)?.[1]?.trim()})(n.value??"")&&r.push(vt(e,n,"template-trackby-required-for-ngfor","*ngFor without trackBy can recreate DOM nodes unnecessarily when list items change."));for(let n of t.blocks)n.name!=="for"||n.parameters.some(i=>{let a=i.expression?.trim()??"";return !!a&&!!/^track\b/.test(a)&&a.replace(/^track\b/,"").trim().length>0})||r.push(vt(e,n,"template-track-required-for-atfor","@for without a track expression can recreate DOM nodes unnecessarily when list items change."));return r.length>0?r:null},{requires:{htmlAst:true}});var Ue="template-no-object-literal-binding",At=engine.createTemplateExpressionRule(Ue,(t,e)=>(function(r){let n=r?[r]:[];for(;n.length>0;){let i=u(n.pop());if(i){if(i.type==="ObjectExpression")return true;for(let a of w(i))n.push(a);}}return false})(t.expression)?[(function(r,n){let i=j(n,r.sourceSpan.start),{line:a,column:l}=n.locator.location(i);return {filePath:n.filePath,ruleName:Ue,message:"Object literals in template bindings create a new reference on every change detection cycle.",line:a,column:l,severity:"warn",fix:x[Ue]}})(t,e)]:null,{requires:{htmlAst:true}});var Fe="template-no-array-literal-binding",St=engine.createTemplateExpressionRule(Fe,(t,e)=>(function(r){let n=r?[r]:[];for(;n.length>0;){let i=u(n.pop());if(i){if(i.type==="ArrayExpression")return true;for(let a of w(i))n.push(a);}}return false})(t.expression)?[(function(r,n){let i=j(n,r.sourceSpan.start),{line:a,column:l}=n.locator.location(i);return {filePath:n.filePath,ruleName:Fe,message:"Array literals in template bindings create a new reference on every change detection cycle.",line:a,column:l,severity:"warn",fix:x[Fe]}})(t,e)]:null,{requires:{htmlAst:true}});var qe=new Map([["bypassSecurityTrustHtml","HTML"],["bypassSecurityTrustScript","Script"],["bypassSecurityTrustStyle","Style"],["bypassSecurityTrustUrl","URL"],["bypassSecurityTrustResourceUrl","Resource URL"]]),Ct=engine.createCallExpressionRule("no-bypass-sanitization",(t,e)=>{let r=(function(a){let l=u(a.callee);if(!l)return null;if(l.type==="Identifier"&&l.name)return qe.has(l.name)?l.name:null;if(b(l)){let s=A(l);return s&&qe.has(s)?s:null}return null})(t);if(!r)return null;let{line:n,column:i}=e.locator.location(v(t));return {filePath:e.filePath,ruleName:"no-bypass-sanitization",message:`\`${r}\` bypasses Angular's ${qe.get(r)} sanitization, which can expose unsafe content.`,line:n,column:i,severity:"error",fix:x["no-bypass-sanitization"]}});var Lr=new Map([["[innerHTML]",{desc:"innerHTML directly renders HTML markup and can execute injected scripts",severity:"error"}],["[outerHTML]",{desc:"outerHTML replaces the element with raw HTML and is susceptible to XSS",severity:"error"}],["[srcdoc]",{desc:"srcdoc embeds raw HTML inside an iframe and can execute injected scripts",severity:"error"}],["[style]",{desc:"binding complex expressions to [style] can enable CSS injection",severity:"warn"}]]),$r=/\|\s*(safeHtml|safeStyle|safeUrl|safeResourceUrl|sanitize|sanitizeHtml|sanitizeUrl|trustHtml|trustStyle|trustUrl|trustResourceUrl|bypassSecurity)\b/i,Br=/\b(getSafeHtml|getSafeUrl|getSafeStyle|getSafeResourceUrl|sanitize|sanitizeHtml|sanitizeUrl|trustHtml|bypassSecurity)\s*\(/i,wt=engine.createTemplateAttributeRule("template-no-unsafe-bindings",(t,e)=>{let r=Lr.get(t.name);if(!r)return null;let n=t.value??"";if($r.test(n)||Br.test(n)||t.name==="[style]"&&!/[.(|?]/.test(n))return null;let{line:i,column:a}=e.locator.location(j(e,t.sourceSpan.start));return {filePath:e.filePath,ruleName:"template-no-unsafe-bindings",message:`\`${t.name}\` binds raw content: ${r.desc}. Unsanitized values can create injection risk.`,line:i,column:a,severity:r.severity,fix:x["template-no-unsafe-bindings"]}},{requires:{htmlAst:true}});var Ur=new Set(["document","window","localStorage","sessionStorage","navigator","location"]),Rt=engine.createAnyAngularClassRule("no-document-access",(t,e)=>{let r=M(t.node);if(r.length===0)return null;let n=(function(s){let p=new Set,c=[...s];for(;c.length;){let o=u(c.pop());if(o){if(o.type==="VariableDeclarator"&&o.init){let f=u(o.init);if(f?.type==="CallExpression"&&f.callee&&O(f.callee,"isPlatformBrowser")){let m=o.id??o.key;m?.type==="Identifier"&&m.name&&p.add(m.name);}}if(o.type==="AssignmentExpression"&&o.right){let f=u(o.right);if(f?.type==="CallExpression"&&f.callee&&O(f.callee,"isPlatformBrowser")){let m=u(o.left);if(m&&b(m)&&u(m.object)?.type==="ThisExpression"){let d=m.property?.name;typeof d=="string"&&d&&p.add(d);}}}for(let f of w(o))c.push(f);}}return p})(r),i=[],a=new Set,l=[...r];for(;l.length;){let s=u(l.pop());if(s){if(s.type==="IfStatement"&&s.test){let p=u(s.test);if(p){let c=(function(o,f){let m=[o];for(;m.length;){let d=u(m.pop());if(d){if(d.type==="CallExpression"&&d.callee&&O(d.callee,"isPlatformBrowser"))return "browser";if(d.type==="CallExpression"&&d.callee&&O(d.callee,"isPlatformServer"))return "server";if(d.type==="UnaryExpression"&&d.operator==="!"&&d.argument){let y=u(d.argument);if(y?.type==="CallExpression"&&y.callee&&O(y.callee,"isPlatformServer"))return "browser"}if(d.type==="Identifier"&&f.has(d.name))return "browser";for(let y of w(d))m.push(y);}}return null})(p,n);if(c==="browser"){s.alternate&&l.push(s.alternate);continue}if(c==="server"){s.consequent&&l.push(s.consequent);continue}}}if(s.type==="CallExpression"&&s.callee){let p=u(s.callee);if(p&&(O(p,"afterNextRender")||O(p,"afterRender"))){let c=s.arguments??[];for(let o=1;o<c.length;o++)l.push(c[o]);continue}}if(b(s)){let p=(function(c){let o=c;for(;o&&b(o);)o=u(o.object);return o?.type==="Identifier"?o.name??null:null})(s);if(p&&Ur.has(p)){let c=s;for(;c&&b(c);)c=u(c.object);let o=v(c);if(o!==void 0&&!a.has(o)){a.add(o);let{line:f,column:m}=e.locator.location(o);i.push({filePath:e.filePath,ruleName:"no-document-access",message:`Direct access to \`${p}\` can run during SSR where browser globals do not exist.`,line:f,column:m,severity:"error",fix:x["no-document-access"],codeExample:N["no-document-access"]});}}}for(let p of w(s))l.push(p);}}return i.length?i:null});var qr=new Set(["nativeElement","getBoundingClientRect","querySelector","querySelectorAll","getElementById","createElement","scrollIntoView","focus","blur","offsetWidth","offsetHeight","clientWidth","clientHeight","scrollTop","scrollLeft","innerHTML","outerHTML","textContent","parentElement","parentNode","firstChild","lastChild","nextSibling","previousSibling","children","ownerDocument","insertBefore","appendChild","removeChild","replaceChild","cloneNode","setAttribute","getAttribute","removeAttribute","hasAttribute","classList","dispatchEvent","contains","matches","closest","getClientRects","addEventListener","removeEventListener","requestAnimationFrame","cancelAnimationFrame"]),zr=["getElementsBy","offset","client","scroll"],Nt=engine.createAnyAngularClassRule("prefer-after-render-over-after-view-init",(t,e)=>{if(t.decoratorName!=="Component"&&t.decoratorName!=="Directive")return null;let r=M(t.node),n=[];for(let i of ["ngAfterViewInit","ngAfterContentInit"]){let a=r.find(c=>c.key?.name===i),l=a?_(a):null;if(!l||!a||!(function(c){let o=[c];for(;o.length;){let f=u(o.pop());if(f){if((f.type==="MemberExpression"||f.type==="StaticMemberExpression"||f.type==="OptionalMemberExpression")&&!f.computed){let m=f.property?.name??"";if(qr.has(m))return true;for(let d of zr)if(m.startsWith(d))return true}if(f.type==="Identifier"&&(f.name==="document"||f.name==="window"))return true;for(let m of w(f))o.push(m);}}return false})(l))continue;let{line:s,column:p}=e.locator.location(v(a));n.push({filePath:e.filePath,ruleName:"prefer-after-render-over-after-view-init",message:`\`${i}\` contains DOM access that can run before browser-only APIs are safe.`,line:s,column:p,severity:"warn",fix:x["prefer-after-render-over-after-view-init"],codeExample:N["prefer-after-render-over-after-view-init"]});}return n.length?n:null});var fe=new Map;function ze(t){return /ngOnDestroy\s*\([^)]*\)\s*\{[^}]*\.unsubscribe\s*\(\)/s.test(t)}var Mt=engine.createCallExpressionRule("rxjs-require-takeUntilDestroyed",(t,e)=>{var r,n;let i;if(!e.filePath.endsWith(".component.ts")&&!e.filePath.endsWith(".directive.ts")&&!/\@(Component|Directive)\s*[\(\{]/.test(e.fileContent)||!ee(t))return null;let a=u(t.callee),l=b(a)?a?.object:null;if(l&&ne(l)||oe(ie(l))||(r=e.filePath,n=e.fileContent,(i=fe.get(r))!==void 0||(i=ze(n),fe.size>=500&&fe.clear(),fe.set(r,i)),i))return null;let s=v(t),{line:p,column:c}=e.locator.location(s);return {filePath:e.filePath,ruleName:"rxjs-require-takeUntilDestroyed",message:"A component subscription without teardown can keep running after the component is destroyed.",line:p,column:c,severity:"error",fix:x["rxjs-require-takeUntilDestroyed"]}});var me=new Map,de="rxjs-no-subscribe-in-component",Pt=engine.createCallExpressionRule(de,(t,e)=>{var r,n;let i;if(!e.filePath.endsWith(".component.ts")&&!e.filePath.endsWith(".directive.ts")&&!/\@(Component|Directive)\s*[\(\{]/.test(e.fileContent)||!ee(t)||(function(o){let f=u(o.callee);if(!f||!b(f))return false;let m=u(f.object);if(!m)return false;if(m.type==="CallExpression"){let d=u(m.callee);if(b(d)&&A(d)==="pipe"&&(Array.isArray(m.arguments)?m.arguments:[]).some(y=>{let g=u(y);if(g?.type!=="CallExpression")return false;let h=u(g.callee)?.name;return h==="take"||h==="first"}))return true}return oe(ie(m))})(t))return null;let a=u(t.callee),l=u(a?.object);if(l&&ne(l)||(r=e.filePath,n=e.fileContent,(i=me.get(r))!==void 0||(i=ze(n),me.size>=500&&me.clear(),me.set(r,i)),i))return null;let s=v(t),{line:p,column:c}=e.locator.location(s);return {filePath:e.filePath,ruleName:de,message:(function(o,f){let m="Open-ended subscriptions in components can outlive the component and make state harder to track.",d=f.crossRef?.templateReferences;if(!d)return m;let y=u(o.callee),g=u(y?.object),h=g?A(g):null;if(!h)return m;let E=h.endsWith("$")?h.slice(0,-1):h;return d.has(h)||d.has(E)?`'${h}' is read by the template, so subscribing manually adds state and teardown work the template can own.`:m})(t,e),line:p,column:c,severity:"error",fix:x[de],codeExample:N[de]}},{requires:{projectContext:true}});var ge="rxjs-avoid-subject-as-event-bus",It=new Set(["Subject","ReplaySubject","AsyncSubject","BehaviorSubject"]),Xr=new Set(["destroy$","destroyed$","ondestroy$","ngondestroy$","unsubscribe$","unsub$","teardown$","dispose$","cleanup$","cleanupsubject$"]),Gr=/state|loading|error|active|selected|open|visible|disabled|count|value|data|hidden|expanded|pending|success|failed/i;function K(t,e,r){let n=[t];for(;n.length;){let i=u(n.pop());if(!i)continue;let a=u(i.callee);if(i.type==="CallExpression"&&A(a)===e){let l=u(a?.object);if(l&&b(l)&&u(l.object)?.type==="ThisExpression"){let s=A(l);s&&r.add(s);}}for(let l of w(i))n.push(l);}}var Ot=engine.createAnyAngularClassRule(ge,(t,e)=>{if(t.decoratorName!=="Component"&&t.decoratorName!=="Directive")return null;let r=M(t.node);if(r.length===0)return null;let n=(function(s){let p=lt(s.sourceText,It),c=new Map;for(let[o,f]of p)for(let m of f)c.set(m,o);return c})(e),i=(function(s){let p=new Set,c=W(s);if(c){let o=_(c);o&&K(o,"next",p);}for(let o of s)if(V(o)){o.kind==="set"&&(function(m){let d=m.decorators;if(!Array.isArray(d))return false;for(let y of d){let g=u(y.expression);if(g?.name==="Input"||u(g?.callee)?.name==="Input")return true}return false})(o)&&K(o,"next",p);let f=o.key?.name;if(f==="ngOnChanges"){let m=_(o);m&&K(m,"next",p);}if(f==="ngOnDestroy"){let m=_(o);m&&K(m,"complete",p);}}else o.type==="PropertyDefinition"&&o.value&&K(o.value,"next",p);return p})(r),a=(function(s){let p=new Set;for(let c of s){let o=V(c)?_(c):c.type==="PropertyDefinition"?c.value:null;o&&K(o,"pipe",p);}return p})(r),l=[];for(let s of r){if(s.type!=="PropertyDefinition"||s.accessibility==="public")continue;let p=u(s.value??s.initializer);if(!p||p.type!=="NewExpression")continue;let c=(function(d,y){let g=u(d);if(!g)return null;if(g.type==="Identifier")return y.get(g.name)??null;if(b(g)){let h=A(g);return h&&It.has(h)?h:null}return null})(p.callee,n),o=s.key?.name??"";if(!c||!o||Xr.has(o.toLowerCase())||i.has(o)||a.has(o))continue;let{line:f,column:m}=e.locator.location(v(s));l.push({filePath:e.filePath,ruleName:ge,message:Gr.test(o)?`${c} '${o}' is used for component UI state, which adds stream overhead to local state updates.`:`${c} '${o}' is acting as a local event bus, which makes component interactions harder to trace.`,line:f,column:m,severity:"warn",fix:x[ge],codeExample:N[ge]});}return l.length?l:null});var He="rxjs-prefer-toSignal-for-template-state",Tt=new Set(["Observable","Subject","BehaviorSubject","ReplaySubject","AsyncSubject"]),Yr=new Set(["destroy$","destroyed$","unsub$","teardown$","dispose$"]),kt=engine.createAnyAngularClassRule(He,(t,e)=>{if(t.metadata?.type!=="Component")return null;let n=e.crossRef?.templateReferences;if(n===void 0)return null;let i=M(t.node),a=[];for(let l of i){if(l.type!=="PropertyDefinition")continue;let s=l.key?.name??"";if(!s||!s.endsWith("$")||Yr.has(s.toLowerCase())||(function(f){let m=f.modifiers,d=f.decorators??(Array.isArray(m)?m.filter(y=>y.type==="Decorator"):void 0);return Array.isArray(d)&&d.some(y=>{let g=u(y.expression);return (g?.type==="CallExpression"?B(g):g?.type==="Identifier"?g.name:null)==="Output"})})(l))continue;let p=s.slice(0,-1);if(!n.has(s)&&!n.has(p)||!(function(f){if(Tt.has((function(g){let h=g.typeAnnotation,E=u(h?.typeAnnotation);if(!E||E.type!=="TSTypeReference"&&E.type!=="TypeReference")return "";let S=E.typeName??E.name;if(typeof S=="string")return S;if(S&&typeof S=="object"){if(S.type==="Identifier")return S.name??"";if(S.type==="TSQualifiedName")return u(S.right)?.name??""}return ""})(f)))return true;let m=u(f.value??f.initializer);if(!m)return false;let d=B(m);if(d==="toSignal"||d==="signal"||d==="computed")return false;if(m.type==="NewExpression"){let g=u(m.callee),h=g?.type==="Identifier"?g.name:A(g);return !!h&&Tt.has(h)}let y=[m];for(;y.length>0;){let g=u(y.pop());if(g){if(g.type==="CallExpression"&&B(g)==="pipe")return true;for(let h of w(g))y.push(h);}}return false})(l))continue;let{line:c,column:o}=e.locator.location(v(l));a.push({filePath:e.filePath,ruleName:He,message:`Observable "${s}" is read by the template, which can add async-pipe churn and weaker signal integration.`,line:c,column:o,severity:"warn",fix:x[He]});}return a.length?a:null},{requires:{projectContext:true,htmlAst:true}});var Ve="toSignal-require-initialValue",jt=engine.createCallExpressionRule(Ve,(t,e)=>{if(!O(t.callee,"toSignal"))return null;let r=Array.isArray(t.arguments)?t.arguments:[],n=r[1]?u(r[1]):null;if(!n||n.type!=="ObjectExpression"||!(function(i){let a=J(i,"initialValue");if(a&&!je(a.value))return true;let l=J(i,"requireSync");return !!l&&ke(l.value)})(n)){let{line:i,column:a}=e.locator.location(v(t));return {filePath:e.filePath,ruleName:Ve,message:"toSignal() can emit undefined before the observable produces a value.",line:i,column:a,severity:"warn",fix:x[Ve]}}return null});var Qr=new Set(["set","update","mutate"]),en=new Set(["setTimeout","setInterval","queueMicrotask","requestAnimationFrame","then","catch","finally","subscribe"]),Dt=engine.createCallExpressionRule("signal-prefer-computed-over-sync-effect",(t,e)=>{if(!O(t.callee,"effect"))return null;let r=te(t),n=r?re(r):null;if(!n)return null;let{hasRead:i,hasWrite:a,hasAsync:l,hasLinked:s,firstWrite:p}=(function(f,m){let d={hasRead:false,hasWrite:false,hasAsync:false,hasLinked:false,firstWrite:null},y=[f];for(;y.length;){let g=u(y.pop());if(g){if((g.type==="AwaitExpression"||g.type==="YieldExpression")&&(d.hasAsync=true),g.type==="CallExpression"){let h=B(g);h&&en.has(h)&&(d.hasAsync=true),h==="linkedSignal"&&(d.hasLinked=true),(function(E){let S=u(E);if(!S||S.type!=="CallExpression")return false;let P=u(S.callee);return b(P)&&Qr.has(A(P)||"")})(g)?(d.hasWrite=true,d.firstWrite||(d.firstWrite=g)):(function(E,S,P){let I=u(E);if(!I||I.type!=="CallExpression"||Array.isArray(I.arguments)&&I.arguments.length>0)return false;let k=u(I.callee);if(!k)return false;if(S.typeChecker)try{let T=G(k,S);if(T){let L=S.typeChecker.getTypeOfSymbolAtLocation(T,T.valueDeclaration);if(L&&S.typeChecker.typeToString(L).includes("Signal"))return !0}}catch{}return b(k)?u(k.object)?.type==="ThisExpression":P&&k.type==="Identifier"})(g,m,true)&&(d.hasRead=true);}for(let h of w(g))y.push(h);}}return d})(n,e);if(!a||!i||l||s)return null;let{line:c,column:o}=e.locator.location(v(p||t));return {filePath:e.filePath,ruleName:"signal-prefer-computed-over-sync-effect",message:"This effect reads reactive values and writes derived state, which adds extra reactive cycles.",line:c,column:o,severity:"warn",fix:x["signal-prefer-computed-over-sync-effect"]}},{requires:{typeChecker:true}});var Lt=new Set(["afterRender","afterNextRender"]),rn=/\bafterNextRender\s*\(|\bafterRender\s*\(/,ye=new Map,$t=engine.createCallExpressionRule("signal-avoid-untracked-overuse",(t,e)=>{if(!O(t.callee,"untracked")||(function(i,a){let l=a.sourceText??a.fileContent;if(typeof l=="string"&&!rn.test(l))return false;let s=i.parent;for(;s;){if(s.type==="CallExpression"&&Lt.has(B(s)))return true;s=s.parent;}let p=v(i);return (function(c){let o=ye.get(c.filePath);if(o!==void 0)return o;let f=c.program;return o=f?(function(m){let d=[],y=m.body,g=Array.isArray(y)?[...y]:[m];for(;g.length;){let h=u(g.pop());if(h){if(h.type==="CallExpression"&&Lt.has(B(h))){let E=u(h.arguments?.[0]);if(E){let S=v(E),P=E.end??E.span?.end??v(E);P>S&&d.push([S,P]);}}for(let E of w(h))g.push(E);}}return d})(f):[],ye.size>=300&&ye.clear(),ye.set(c.filePath,o),o})(a).some(([c,o])=>p>=c&&p<o)})(t,e))return null;let{line:r,column:n}=e.locator.location(v(t));return {filePath:e.filePath,ruleName:"signal-avoid-untracked-overuse",message:"untracked() hides this read from dependency tracking, which can mask stale reactive state.",line:r,column:n,severity:"warn",fix:x["signal-avoid-untracked-overuse"]}});var he="prefer-inject-over-constructor-di",on=new Set(["http","router","route","cdr","cdref","changedetectorref","injector","ngzone","zone","renderer","renderer2","elementref","el","document","platform","location","dialog","snack","toast","store","facade","logger","translate","i18n","auth","api","client","matdialog","overlay","breakpointobserver","snackbar","matsnackbar","bottomsheet","matbottomsheet","clipboard","directionality","focusmonitor","mediamatcher","viewportruler","scrolldispatcher","dragdrop","liveannouncer","activatedroute","fb","firestore","angularfire","formbuilder","titleservice","metaservice","title","meta","sanitizer","domsanitizer","compiler","applicationref","componentfactoryresolver","viewcontainerref","templateref","destroyref"]),an=new Set(["data","config","options","opts","params","payload","input","value","values","items","item","context","ctx","model","vm","state","initialstate","result","name","id","label","text","title","message","url","path","index","count","size","length","width","height","color","type","key","mode","flag","enabled","disabled","visible","hidden"]),sn=["Service","Facade","Store","Client","Repository","Adapter","Manager","Controller","Provider","Registry","Logger","Router","ActivatedRoute","ChangeDetectorRef","DestroyRef","Injector","NgZone","Renderer2","ElementRef","HttpClient","ViewContainerRef","TemplateRef","ComponentFactoryResolver","ApplicationRef","MatDialog","MatDialogRef","MatSnackBar","MatBottomSheet","Overlay","OverlayRef","BreakpointObserver","Clipboard","FocusMonitor","MediaMatcher","ScrollDispatcher","DragDrop","LiveAnnouncer","Directionality","ViewportRuler","FormBuilder","DomSanitizer","Title","Meta","Dispatcher","Gateway","Handler","Interceptor","Guard","Resolver","Factory","Strategy","Validator"],ln=new Set(["string","number","boolean","symbol","bigint","any","unknown","void"]),Bt=engine.createAnyAngularClassRule(he,(t,e)=>{let r=W(M(t.node));if(!r)return null;let n=ae(r.value??r).filter(s=>(function(p,c){let o=u(p);if(!o)return false;if(Array.isArray(o.decorators)&&o.decorators.length>0||o.accessibility||o.readonly)return true;if(c.typeChecker){let d=G(p,c);if(d&&pt(d))return true}let f=(X(p)||"").toLowerCase().trim();if(!f||an.has(f))return false;if(on.has(f))return true;let m=(q(p)||"").trim();return !(!m||ln.has(m.toLowerCase()))&&sn.some(d=>m.endsWith(d))})(s,e));if(n.length===0)return null;let{line:i,column:a}=e.locator.location(v(r)),l=n.map(s=>`${X(s)}: ${q(s)}`).join(", ");return {filePath:e.filePath,ruleName:he,message:`Constructor dependency injection makes class setup less composable than inject().${l?` Offending params: ${l}.`:""}`,line:i,column:a,severity:"warn",fix:x[he],codeExample:N[he]}},{requires:{typeChecker:true}});var xe="signal-prefer-input-signal";function pn(t){let e=u(t.expression??t);if(!e)return false;if(e.type==="Identifier")return e.name==="Input";if(e.type==="CallExpression"){let r=u(e.callee);return r?.type==="Identifier"&&r.name==="Input"}return false}var _t=engine.createAnyAngularClassRule(xe,(t,e)=>{if(t.decoratorName!=="Component"&&t.decoratorName!=="Directive")return null;let r=M(t.node),n=[],i=e.project?.standaloneComponents?.has(e.filePath)??false;for(let a of r){let l=u(a);if(l&&(l.type==="PropertyDefinition"||l.type==="AccessorProperty")&&Array.isArray(l.decorators)&&l.decorators.some(pn)){let s=v(l),{line:p,column:c}=e.locator.location(s),o=u(l.key),f=(o?.type==="Identifier"?o.name:o?.type==="Literal"?String(o.value):"")||"(unknown)",m=`'${f}' uses @Input(), which keeps this input outside Angular's signal graph.`;i&&(m+=" Standalone declarations benefit most from signal inputs."),n.push({filePath:e.filePath,ruleName:xe,message:m,line:p,column:c,severity:i?"error":"warn",fix:x[xe],codeExample:N[xe]});}}return n.length>0?n:null},{requires:{projectContext:true}});var be="signal-prefer-output-function",Ut=engine.createAnyAngularClassRule(be,(t,e)=>{if(t.decoratorName!=="Component"&&t.decoratorName!=="Directive")return null;let r=M(t.node),n=[];for(let i of r){let a=u(i);if(a&&(a.type==="PropertyDefinition"||a.type==="AccessorProperty")&&Array.isArray(a.decorators)&&a.decorators.some(l=>{let s=u(l.expression??l);if(!s)return false;if(s.type==="Identifier")return s.name==="Output";if(s.type==="CallExpression"){let p=u(s.callee);return p?.type==="Identifier"&&p.name==="Output"}return false})&&(function(l,s){let p=u(l.value);if(p?.type==="NewExpression"){let o=u(p.callee);if(o?.type==="Identifier"&&o.name==="EventEmitter")return true}let c=u(l.typeAnnotation);if(c&&q(c).includes("EventEmitter"))return true;if(s.typeChecker){let o=G(l,s);if(o&&(typeof o.getName=="function"?o.getName():"").includes("EventEmitter"))return true}return false})(a,e)){let l=v(a),{line:s,column:p}=e.locator.location(l),c=u(a.key),o=(c?.type==="Identifier"?c.name:c?.type==="Literal"?String(c.value):"")||"(unknown)";n.push({filePath:e.filePath,ruleName:be,message:`'${o}' uses @Output() EventEmitter, which adds boilerplate compared with output().`,line:s,column:p,severity:"warn",fix:x[be],codeExample:N[be]});}}return n.length>0?n:null},{requires:{typeChecker:true}});var ve="signal-prefer-model";function Ft(t,e){return Array.isArray(t.decorators)&&t.decorators.some(r=>{let n=u(r.expression??r);if(!n)return false;if(n.type==="Identifier")return n.name===e;if(n.type==="CallExpression"){let i=u(n.callee);return i?.type==="Identifier"&&i.name===e}return false})}function qt(t,e){let r=u(t.value);if(!r||r.type!=="CallExpression")return false;let n=u(r.callee);if(!n)return false;if(n.type==="Identifier")return n.name===e;if(b(n)){let i=u(n.object);return i?.type==="Identifier"&&i.name===e}return false}var zt=engine.createAnyAngularClassRule(ve,(t,e)=>{if(t.decoratorName!=="Component"&&t.decoratorName!=="Directive")return null;let r=M(t.node),n=new Map,i=new Map;for(let l of r){let s=u(l);if(!s||s.type!=="PropertyDefinition"&&s.type!=="AccessorProperty")continue;let p=(function(c){let o=u(c.key);return o?o.type==="Identifier"?o.name??null:o.type==="Literal"?String(o.value):null:null})(s);p&&((Ft(s,"Input")||qt(s,"input"))&&n.set(p,s),(Ft(s,"Output")||qt(s,"output"))&&i.set(p,s));}let a=[];for(let[l,s]of i){if(!l.endsWith("Change"))continue;let p=l.slice(0,-6),c=n.get(p);if(c){let o=v(c),{line:f,column:m}=e.locator.location(o);a.push({filePath:e.filePath,ruleName:ve,message:`The \`${p}\` / \`${l}\` pair implements two-way binding with extra wiring that model() avoids.`,line:f,column:m,severity:"warn",fix:x[ve],codeExample:N[ve]});}}return a.length>0?a:null});var dn=new Map([["*ngIf","@if"],["*ngFor","@for"],["*ngSwitch","@switch"],["[ngSwitch]","@switch"],["*ngSwitchCase","@case"],["*ngSwitchDefault","@default"]]),We="template-prefer-control-flow",Ht=engine.createTemplateAttributeRule(We,(t,e)=>{var r;let n=(r=t.name,dn.get(r)??null);if(!n)return null;let i=j(e,t.sourceSpan.start),{line:a,column:l}=e.locator.location(i);return {filePath:e.filePath,ruleName:We,message:`\`${t.name}\` uses legacy structural directive syntax, so it misses the \`${n}\` control flow benefits.`,line:a,column:l,severity:"error",fix:x[We]}},{requires:{htmlAst:true}});var Vt=new WeakMap,Wt=engine.createTemplateExpressionRule("template-no-async-pipe-duplication",(t,e)=>{let r,n,i=(function c(o){let f=u(o);if(!f)return null;if(f.type==="BinaryExpression"&&f.operator==="|"){let m=u(f.right);return m?.type==="Identifier"&&m.name==="async"?u(f.left):c(f.left)}return null})(t.expression),a=i?(function c(o,f=0){if(!o||f>10)return null;if(o.type==="Identifier")return o.name;if(o.type==="ThisExpression")return "this";if(o.type==="Literal"){let m=o.value;return m===null?"null":typeof m=="string"||typeof m=="number"||typeof m=="boolean"||typeof m=="bigint"?String(m):null}if(o.type==="CallExpression"){let m=c(o.callee,f+1),d=(Array.isArray(o.arguments)?o.arguments:[]).map(y=>c(y,f+1)??"?");return `${m}(${d.join(",")})`}if(b(o)){let m=c(o.object,f+1),d=A(o);if(m&&d)return `${m}.${d}`}if(o.type==="UnaryExpression")return `${o.operator}${c(o.argument,f+1)}`;if(o.type==="ConditionalExpression")return `${c(o.test,f+1)}?${c(o.consequent,f+1)}:${c(o.alternate,f+1)}`;if(o.type==="OptionalExpression"||o.type==="ChainExpression")return c(o.expression,f+1);if(o.type==="LogicalExpression"){let m=c(o.left,f+1),d=c(o.right,f+1);if(m&&d)return `${m}${o.operator}${d}`}if(o.type==="BinaryExpression"&&o.operator!=="|"){let m=c(o.left,f+1),d=c(o.right,f+1);if(m&&d)return `${m}${o.operator}${d}`}if(o.type==="ArrayExpression")return `[${(o.elements??[]).map(d=>c(d,f+1)??"?").join(",")}]`;if(o.type==="ObjectExpression")return `{${(o.properties??[]).map(d=>{let y=d.key?c(d.key,f+1):"?",g=d.value?c(d.value,f+1):"?";return `${y}:${g}`}).join(",")}}`;if(o.type==="TemplateLiteral")return "`tmpl`";if(o.type==="AssignmentExpression"){let m=c(o.left,f+1),d=c(o.right,f+1);if(m&&d)return `${m}${o.operator}${d}`}return null})(i):null;if(!a)return null;let l=(r=e.template,n=r?.templateStartOffset,`${e.filePath}@${typeof n=="number"&&Number.isFinite(n)?n:0}`),s=Vt.get(e);s||(s=new Map,Vt.set(e,s));let p=s.get(l);if(p||(p=new Set,s.set(l,p)),p.has(a)){let c=j(e,t.sourceSpan.start),{line:o,column:f}=e.locator.location(c);return {filePath:e.filePath,ruleName:"template-no-async-pipe-duplication",message:`Duplicate async pipe subscriptions for "${a}" can create repeated work and inconsistent loading states.`,line:o,column:f,severity:"warn",fix:x["template-no-async-pipe-duplication"]}}return p.add(a),null},{requires:{htmlAst:true}});var hn=new Set(["fdescribe","fit","describe.only","it.only","test.only","context.only","xdescribe","xit","xtest","xcontext"]),xn=new Set(["xdescribe","xit","xtest","xcontext"]);function Xt(t){return t.type==="Identifier"?t.name??null:null}var Gt=engine.createCallExpressionRule("spec-no-focused-test",(t,e)=>{var r;let n;if(!((r=e.filePath).endsWith(".spec.ts")||r.endsWith(".test.ts")))return null;let i=u(t.callee);if(!i)return null;let a=(n=Xt(i)||(function(o){if(o.type==="MemberExpression"||o.type==="StaticMemberExpression"||o.type==="OptionalMemberExpression"){let f=u(o.object),m=o.property?.name??"",d=(f?.type==="Identifier"?f.name:"")??"";return d?`${d}.${m}`:null}return null})(i))&&hn.has(n)?n:null;if(!a){if(Xt(i)==="pending"){let o=v(t),{line:f,column:m}=e.locator.location(o);return {filePath:e.filePath,ruleName:"spec-no-focused-test",message:"`pending()` disables the test body, so the committed suite can miss coverage.",line:f,column:m,severity:"error",fix:x["spec-no-focused-test"]}}return null}let l=v(t),{line:s,column:p}=e.locator.location(l),c=xn.has(a)?`\`${a}\` disables a test that may be forgotten.`:`\`${a}\` is a focused test helper that can skip the rest of the suite in CI.`;return {filePath:e.filePath,ruleName:"spec-no-focused-test",message:c,line:s,column:p,severity:"error",fix:x["spec-no-focused-test"]}},{dependencyType:"spec",requires:{tsAst:true}});var Kt={"prefer-on-push-component-change-detection":"Add `changeDetection: ChangeDetectionStrategy.OnPush` to the component metadata.","template-no-call-expression":"Move the value to a pipe, computed signal, or cached component property.","rxjs-no-subscribe-in-component":"Use `toSignal()` or the `async` pipe for view state; add `takeUntilDestroyed()` for long-lived imperative subscriptions.","template-trackby-required-for-ngfor":"Add `trackBy` to `*ngFor`, or use `track` when migrating to `@for`.","template-no-object-literal-binding":"Move the object to a component property, signal, computed value, or pure pipe.","template-no-array-literal-binding":"Move the array to a component property, signal, computed value, or pure pipe.","toSignal-require-initialValue":"Pass `{ initialValue }` or `{ requireSync: true }` so the signal has a stable type.","rxjs-avoid-subject-as-event-bus":"Use `signal()` for component UI state; move complex async pipelines into a service.","signal-no-side-effects-in-computed":"Move side effects to an `effect()` or method, and keep `computed()` pure.","signal-no-writes-in-computed":"Move `.set()` or `.update()` calls to an `effect()` or event handler.","prefer-inject-over-constructor-di":"Replace constructor parameters with field initializers that call `inject()`.","component-no-manual-detect-changes":"Use signals, `async` pipe, or input-driven state instead of calling `detectChanges()` or `markForCheck()`.","rxjs-require-takeUntilDestroyed":"Add `takeUntilDestroyed()` or another teardown operator before `subscribe()`.","template-no-async-pipe-duplication":"Store the async value once with `@if (... | async; as value)` or a view-model signal.","rxjs-prefer-toSignal-for-template-state":"Convert template-used observables to `toSignal()` and read them as signals in the template.","signal-effect-must-be-destroy-scoped":"Create the effect in an injection context or pass an explicit `{ injector }`.","signal-no-effect-in-constructor":"Move the `effect()` call to a field initializer.","signal-prefer-computed-over-sync-effect":"Replace the write-producing `effect()` with a `computed()` signal.","signal-avoid-untracked-overuse":"Remove `untracked()` unless the dependency must be intentionally ignored.","template-prefer-control-flow":"Replace the legacy directive with the matching `@if`, `@for`, or `@switch` block.","signal-prefer-input-signal":"Replace `@Input()` with `input()` or `input.required()`.","signal-prefer-output-function":"Replace `@Output() EventEmitter` with `output<T>()`.","no-bypass-sanitization":"Use Angular sanitization or a trusted server-side sanitizer instead of `bypassSecurityTrust*`.","rxjs-no-nested-subscribe":"Flatten the stream with `switchMap`, `mergeMap`, `concatMap`, or `exhaustMap`.","no-document-access":"Inject `DOCUMENT` or move browser-only DOM work into `afterNextRender()`.","template-no-unsafe-bindings":"Sanitize the value before binding, or replace the raw HTML binding with structured template content.","signal-prefer-model":"Replace the `@Input()` / `@Output()Change` pair with `model()`.","prefer-after-render-over-after-view-init":"Move browser-only DOM access into `afterNextRender()`.","spec-no-focused-test":"Replace focused or disabled test helpers with normal `describe` and `it` calls."},pa={"prefer-on-push-component-change-detection":`// Before:
217
+ @Component({ selector: 'app-foo', template: '...' })
218
+ export class FooComponent { }
219
+
220
+ // After:
221
+ @Component({
222
+ selector: 'app-foo',
223
+ template: '...',
224
+ changeDetection: ChangeDetectionStrategy.OnPush,
225
+ })
226
+ export class FooComponent { }`,"rxjs-no-subscribe-in-component":`// Reactive state (data derived from a stream) \u2192 use toSignal() or async pipe:
227
+ // Before:
228
+ ngOnInit() { this.data$.subscribe(val => this.value = val); }
229
+ // After:
230
+ value = toSignal(this.data$, { initialValue: defaultVal });
231
+
232
+ // Imperative action (user-triggered, one-shot) \u2192 subscription is fine, add teardown:
233
+ // Before:
234
+ onSave() { this.api.save(this.form.value).subscribe(res => this.notify(res)); }
235
+ // After (HTTP auto-completes \u2014 takeUntilDestroyed optional but harmless):
236
+ onSave() {
237
+ this.api.save(this.form.value)
238
+ .pipe(takeUntilDestroyed(this.destroyRef))
239
+ .subscribe(res => this.notify(res));
240
+ }`,"rxjs-avoid-subject-as-event-bus":`// Case A \u2014 UI state: replace with signal()
241
+ // Before:
242
+ private loading$ = new Subject<boolean>();
243
+ // After:
244
+ loading = signal(false);
245
+
246
+ // Case B \u2014 complex async pipeline: move to a service
247
+ // Before (in component):
248
+ private search$ = new Subject<string>();
249
+ ngOnInit() { this.search$.pipe(debounceTime(300), switchMap(...)).subscribe(...); }
250
+ // After:
251
+ // SearchService owns search$ and the pipeline; component calls searchSvc.search(term)
252
+
253
+ // Note: @Input() setter bridges are intentionally exempt:
254
+ // @Input() set query(v: string) { this.search$.next(v); } \u2190 allowed`,"prefer-inject-over-constructor-di":`// Before:
255
+ constructor(private http: HttpClient, private router: Router) { }
256
+
257
+ // After:
258
+ private http = inject(HttpClient);
259
+ private router = inject(Router);`,"component-no-manual-detect-changes":`// Before:
260
+ this.cdr.detectChanges();
261
+ this.cdr.markForCheck();
262
+
263
+ // After:
264
+ // Use signals for reactive state:
265
+ count = signal(0);
266
+ // Template automatically updates when signal changes`,"rxjs-require-takeUntilDestroyed":`// Before:
267
+ this.data$.subscribe(val => this.process(val));
268
+
269
+ // After:
270
+ this.data$.pipe(
271
+ takeUntilDestroyed(this.destroyRef)
272
+ ).subscribe(val => this.process(val));`,"template-no-async-pipe-duplication":`// Before:
273
+ <div>{{ user$ | async }}</div>
274
+ <span>{{ user$ | async }}</span>
275
+
276
+ // After:
277
+ @if (user$ | async; as user) {
278
+ <div>{{ user }}</div>
279
+ <span>{{ user }}</span>
280
+ }`,"signal-no-side-effects-in-computed":`// Before:
281
+ total = computed(() => {
282
+ this.logger.log('computing'); // side effect!
283
+ return this.price() * this.qty();
284
+ });
285
+
286
+ // After:
287
+ total = computed(() => this.price() * this.qty());`,"signal-no-writes-in-computed":`// Before:
288
+ derived = computed(() => {
289
+ const val = this.source();
290
+ this.other.set(val * 2); // write inside computed!
291
+ return val;
292
+ });
293
+
294
+ // After:
295
+ derived = computed(() => this.source());
296
+ // Use effect() for the write:
297
+ syncEffect = effect(() => this.other.set(this.source() * 2));`,"signal-effect-must-be-destroy-scoped":`// Before:
298
+ ngAfterViewInit() {
299
+ effect(() => console.log(this.count())); // no injection context!
300
+ }
301
+
302
+ // After (option A - field initializer):
303
+ logEffect = effect(() => console.log(this.count()));
304
+
305
+ // After (option B - explicit injector):
306
+ ngAfterViewInit() {
307
+ effect(() => console.log(this.count()), { injector: this.injector });
308
+ }`,"signal-no-effect-in-constructor":`// Before:
309
+ constructor() {
310
+ effect(() => console.log(this.count()));
311
+ }
312
+
313
+ // After:
314
+ logEffect = effect(() => console.log(this.count()));`,"signal-prefer-computed-over-sync-effect":`// Before:
315
+ logEffect = effect(() => {
316
+ const total = this.price() * this.qty();
317
+ this.total.set(total);
318
+ });
319
+
320
+ // After:
321
+ total = computed(() => this.price() * this.qty());`,"toSignal-require-initialValue":`// Before:
322
+ data = toSignal(this.data$); // Signal<T | undefined>
323
+
324
+ // After:
325
+ data = toSignal(this.data$, { initialValue: [] }); // Signal<T>`,"template-no-call-expression":`// Before:
326
+ <div>{{ getLabel(item) }}</div>
327
+
328
+ // After (option A - pipe):
329
+ <div>{{ item | labelPipe }}</div>
330
+
331
+ // After (option B - signal):
332
+ label = computed(() => this.getLabel(this.item()));`,"template-no-object-literal-binding":`// Before:
333
+ <app-child [config]="{ color: 'red', size: 10 }"></app-child>
334
+
335
+ // After:
336
+ childConfig = signal({ color: 'red', size: 10 });
337
+ // template: <app-child [config]="childConfig()"></app-child>`,"template-no-array-literal-binding":`// Before:
338
+ <app-child [items]="[1, 2, 3]"></app-child>
339
+
340
+ // After:
341
+ items = signal([1, 2, 3]);
342
+ // template: <app-child [items]="items()"></app-child>`,"template-trackby-required-for-ngfor":`// Before:
343
+ <div *ngFor="let item of items">{{ item.name }}</div>
344
+
345
+ // After:
346
+ <div *ngFor="let item of items; trackBy: trackById">{{ item.name }}</div>
347
+
348
+ // Or with @for (Angular 17+):
349
+ @for (item of items; track item.id) {
350
+ <div>{{ item.name }}</div>
351
+ }`,"rxjs-prefer-toSignal-for-template-state":`// Before:
352
+ data$ = this.http.get('/api/data').pipe(shareReplay(1));
353
+ // template: {{ data$ | async }}
354
+
355
+ // After:
356
+ data = toSignal(this.http.get('/api/data'), { initialValue: null });
357
+ // template: {{ data() }}`,"signal-avoid-untracked-overuse":`// Acceptable:
358
+ effect(() => {
359
+ const value = this.count();
360
+ untracked(() => this.analytics.track(value));
361
+ });
362
+
363
+ // Questionable (review if untracked is needed):
364
+ const val = untracked(() => this.count());`,"template-prefer-control-flow":`// Before:
365
+ <div *ngIf="isLoggedIn">Welcome</div>
366
+ <li *ngFor="let item of items; trackBy: trackById">{{ item.name }}</li>
367
+
368
+ // After:
369
+ @if (isLoggedIn) { <div>Welcome</div> }
370
+ @for (item of items; track item.id) { <li>{{ item.name }}</li> }`,"signal-prefer-input-signal":`// Before:
371
+ @Input() title: string = '';
372
+ @Input() required count!: number;
373
+
374
+ // After:
375
+ title = input('');
376
+ count = input.required<number>();`,"signal-prefer-output-function":`// Before:
377
+ @Output() selected = new EventEmitter<Item>();
378
+
379
+ // After:
380
+ selected = output<Item>();`,"rxjs-no-nested-subscribe":`// Before:
381
+ this.user$.subscribe(user => {
382
+ this.posts$.subscribe(posts => {
383
+ this.render(user, posts);
384
+ });
385
+ });
386
+
387
+ // After:
388
+ this.user$.pipe(
389
+ switchMap(user => this.posts$.pipe(map(posts => ({ user, posts }))))
390
+ ).subscribe(({ user, posts }) => this.render(user, posts));`,"no-document-access":`// Before:
391
+ ngOnInit() {
392
+ document.title = this.title;
393
+ }
394
+
395
+ // After (inject DOCUMENT):
396
+ private doc = inject(DOCUMENT);
397
+ ngOnInit() { this.doc.title = this.title; }
398
+
399
+ // Or (SSR-safe browser-only block):
400
+ afterNextRenderEffect = afterNextRender(() => {
401
+ document.title = this.title;
402
+ });`,"signal-prefer-model":`// Before:
403
+ @Input() value: string = '';
404
+ @Output() valueChange = new EventEmitter<string>();
405
+
406
+ // After:
407
+ value = model('');
408
+ // Parent template: <app-input [(value)]="parentValue" />`,"prefer-after-render-over-after-view-init":`// Before:
409
+ ngAfterViewInit() {
410
+ this.el.nativeElement.focus();
411
+ }
412
+
413
+ // After:
414
+ constructor() {
415
+ afterNextRender(() => {
416
+ this.el.nativeElement.focus();
417
+ });
418
+ }`,"spec-no-focused-test":`// Before (focused \u2014 skips all other tests):
419
+ fdescribe('MyComponent', () => {
420
+ fit('should render', () => { ... });
421
+ });
422
+
423
+ // Before (disabled \u2014 easily forgotten):
424
+ xdescribe('MyComponent', () => {
425
+ xit('should render', () => { ... });
426
+ });
427
+
428
+ // After:
429
+ describe('MyComponent', () => {
430
+ it('should render', () => { ... });
431
+ });`};var Ee={name:"ngcompass:recommended",description:"High-confidence rules that prevent real bugs and regressions in any Angular project",rules:{"component-no-manual-detect-changes":"error","rxjs-no-nested-subscribe":"error","signal-no-side-effects-in-computed":"error","signal-effect-must-be-destroy-scoped":"error","rxjs-no-subscribe-in-component":"error","rxjs-require-takeUntilDestroyed":"error","prefer-on-push-component-change-detection":"error","template-no-call-expression":"error","template-trackby-required":"error","template-no-object-literal-binding":"warn","template-no-array-literal-binding":"warn","template-no-async-pipe-duplication":"warn","no-bypass-sanitization":"error","template-no-unsafe-bindings":"error","spec-no-focused-test":"error"}};var Ae={name:"ngcompass:strict",description:"All rules at error severity \u2014 zero tolerance mode",rules:{"component-no-manual-detect-changes":"error","rxjs-no-nested-subscribe":"error","signal-no-side-effects-in-computed":"error","signal-effect-must-be-destroy-scoped":"error","prefer-on-push-component-change-detection":"error","prefer-inject-over-constructor-di":"error","rxjs-no-subscribe-in-component":"error","rxjs-require-takeUntilDestroyed":"error","rxjs-avoid-subject-as-event-bus":"error","rxjs-prefer-toSignal-for-template-state":"error","toSignal-require-initialValue":"error","signal-prefer-computed-over-sync-effect":"error","signal-avoid-untracked-overuse":"error","signal-prefer-input-signal":"error","signal-prefer-output-function":"error","signal-prefer-model":"error","template-no-call-expression":"error","template-trackby-required":"error","template-no-object-literal-binding":"error","template-no-array-literal-binding":"error","template-no-async-pipe-duplication":"error","template-prefer-control-flow":"error","no-bypass-sanitization":"error","template-no-unsafe-bindings":"error","no-document-access":"error","prefer-after-render-over-after-view-init":"error","spec-no-focused-test":"error"}};var Y={name:"ngcompass:all",description:"All available rules enabled at their default severity",rules:{"prefer-on-push-component-change-detection":"error","component-no-manual-detect-changes":"error","prefer-inject-over-constructor-di":"warn","rxjs-no-subscribe-in-component":"error","rxjs-require-takeUntilDestroyed":"error","rxjs-avoid-subject-as-event-bus":"warn","rxjs-prefer-toSignal-for-template-state":"warn","toSignal-require-initialValue":"warn","rxjs-no-nested-subscribe":"warn","signal-no-side-effects-in-computed":"error","signal-prefer-computed-over-sync-effect":"warn","signal-effect-must-be-destroy-scoped":"error","signal-avoid-untracked-overuse":"warn","signal-prefer-input-signal":"warn","signal-prefer-output-function":"warn","signal-prefer-model":"warn","template-no-call-expression":"error","template-trackby-required":"error","template-no-object-literal-binding":"warn","template-no-array-literal-binding":"warn","template-no-async-pipe-duplication":"warn","template-prefer-control-flow":"warn","no-bypass-sanitization":"warn","template-no-unsafe-bindings":"warn","no-document-access":"warn","prefer-after-render-over-after-view-init":"warn","spec-no-focused-test":"error"}};var Se={name:"ngcompass:performance",description:"Rules that directly impact Angular rendering and change-detection performance",rules:{"prefer-on-push-component-change-detection":"error","component-no-manual-detect-changes":"error","template-no-call-expression":"error","template-trackby-required":"error","template-no-object-literal-binding":"warn","template-no-array-literal-binding":"warn","template-no-async-pipe-duplication":"warn","rxjs-prefer-toSignal-for-template-state":"warn"}};var Ce={name:"ngcompass:reactivity",description:"Signals correctness and RxJS \u2192 Signals migration rules for Angular 17+ projects",rules:{"rxjs-no-nested-subscribe":"error","rxjs-no-subscribe-in-component":"error","rxjs-require-takeUntilDestroyed":"error","rxjs-avoid-subject-as-event-bus":"warn","rxjs-prefer-toSignal-for-template-state":"warn","toSignal-require-initialValue":"warn","signal-no-side-effects-in-computed":"error","signal-effect-must-be-destroy-scoped":"error","signal-prefer-computed-over-sync-effect":"warn","signal-avoid-untracked-overuse":"warn","signal-prefer-input-signal":"warn","signal-prefer-output-function":"warn","signal-prefer-model":"warn"}};var we={name:"ngcompass:security",description:"XSS and sanitization bypass prevention rules",rules:{"no-bypass-sanitization":"error","template-no-unsafe-bindings":"error"}};var Re={name:"ngcompass:ssr",description:"Platform safety rules for Angular SSR / Universal applications",rules:{"no-document-access":"warn","prefer-after-render-over-after-view-init":"warn"}};var Ne=new Map([["recommended",Ee],["strict",Ae],["performance",Se],["reactivity",Ce],["security",we],["ssr",Re],["all",Y]]),bn=t=>{let e=t.replace(/^ngcompass:/,"");return Ne.has(e)},vn=t=>{let e=t.replace(/^ngcompass:/,"");return Ne.get(e)};function Xe(t){let e=[];for(let[r,n]of Ne)t in n.rules&&e.push(r);return e}var Pe=class{_handlers=new Map;_meta=new Map;register(e,r={}){if(this._handlers.has(e.name)&&!r.allowOverride)throw Error(`[ngcompass] Rule "${e.name}" is already registered. Use allowOverride: true to replace an existing rule intentionally.`);this._handlers.set(e.name,e.handler),e.meta&&this._meta.set(e.name,e.meta);}get(e){return this._handlers.get(e)}has(e){return this._handlers.has(e)}getRuleNames(){return Array.from(this._handlers.keys())}getAll(){return this._handlers}getMeta(e){return this._meta.get(e)}getMetadata(e){if(!this._handlers.has(e))return;let r=this._meta.get(e)??{};return {name:e,description:r.description||`Rule: ${e}`,category:r.category||"general",dependencyType:r.dependencyType||"standalone",requires:{tsAst:true,...r.requires},filePatterns:r.filePatterns}}getRegistryEntry(e){let r=this.getMetadata(e);if(r)return {name:e,metadata:r,defaultConfig:{severity:"warn",options:{}}}}toReadonlyMap(){let e=new Map;for(let r of this._handlers.keys()){let n=this.getRegistryEntry(r);n&&e.set(r,n);}return e}get size(){return this._handlers.size}},Me=null,$=()=>(Me||(Me=new Pe),Me),En=()=>{Me=null;},Ge=t=>$().has(t),Ke=t=>$().getMetadata(t),An=()=>$().getRuleNames(),Sn=()=>$().toReadonlyMap();function Cn(){let t=$(),e=[];for(let r of t.getRuleNames()){let n=t.getMetadata(r);e.push({name:r,description:Kt[r]??n?.description??`Rule: ${r}`,domain:n?.category??"general",severity:Y.rules[r]??"warn",presets:Xe(r)});}return e}var C=(t,e)=>{let r={name:t.name,handler:t,meta:{dependencyType:"component",...t.meta,category:e??t.meta?.category??"best-practice",requires:{...t.meta?.requires}}};$().register(r),common.debug("engine",`Registered rule: ${t.name}`);},Rn=t=>$().has(t);var Nn=(t,e)=>{let r=$(),n=[];for(let l of t){let s=r.get(l);s&&n.push(s);}if(n.length===0)return [];common.debug("engine",`Executing ${n.length} rules in single pass on ${e.filePath}`);let{results:i,performance:a}=engine.runSinglePassAnalysis(n,e);return common.debug("engine",`Single-pass complete: ${a.traversalMs.toFixed(2)}ms, ${a.nodesVisited} nodes`),common.debug("engine",`Cache hit rate: ${(a.cacheStats.hits/(a.cacheStats.hits+a.cacheStats.misses||1)*100).toFixed(1)}%`),a.budgetViolations.length>0&&common.debug("engine","Performance budget violations:",a.budgetViolations),i};function Mn(){C(ft,"correctness"),C(mt,"correctness"),C(dt,"correctness"),C(yt,"correctness"),C(ht,"performance"),C(bt,"performance"),C(Et,"performance"),C(At,"performance"),C(St,"performance"),C(Ct,"security"),C(wt,"security"),C(Rt,"ssr"),C(Nt,"ssr"),C(Pt,"reactivity"),C(Mt,"reactivity"),C(Ot,"reactivity"),C(kt,"reactivity"),C(jt,"reactivity"),C(Dt,"reactivity"),C($t,"reactivity"),C(Bt,"modern-api"),C(_t,"modern-api"),C(Ut,"modern-api"),C(zt,"modern-api"),C(Ht,"template"),C(Wt,"template"),C(Gt,"testing");}var Yt=new Map([["recommended",Ee],["strict",Ae],["performance",Se],["reactivity",Ce],["security",we],["ssr",Re],["all",Y]]),Jt=t=>{let e=t.replace(/^ngcompass:/,"");return Yt.has(e)},Zt=t=>{let e=t.replace(/^ngcompass:/,"");return Yt.get(e)};var Qt=async(t,e=process.cwd())=>{if(common.debug("loader",`Loading preset: ${t}`),Jt(t)){let r=Zt(t);return r?(common.debug("loader",`Loaded built-in preset: ${t}`),common.Ok(r)):common.Err(Error(`Built-in preset not found: ${t}`))}try{let r=In__default.default.resolve(e,t);common.debug("loader",`Loading preset from file: ${r}`);let n=await Pn__default.default.readFile(r,"utf8"),i=JSON.parse(n);return common.debug("loader",`Loaded file-based preset: ${t}`),common.Ok(i)}catch(r){return common.Err(Error(`Failed to load preset "${t}": ${r.message}`))}},Te=async(t,e,r=[])=>{if(!t)return common.Ok([]);let n=Array.isArray(t)?t:[t],i=[];for(let a of n){if(r.includes(a)){let p=[...r,a].join(" -> ");return common.Err(Error(`Circular extends detected: ${p}`))}let l=await Qt(a,e);if(!l.ok)return l;let s=l.data;if(s.extends){let p=await Te(s.extends,e,[...r,a]);if(!p.ok)return p;i.push(...p.data);}i.push(s);}return common.Ok(i)};var z=t=>typeof t=="string"?{severity:t,options:{}}:{severity:t.severity,options:t.options||{}},Je=t=>z(t).severity!=="off",On=t=>{let e=new Map;for(let[r,n]of Object.entries(t))e.set(r,z(n));return e};var Ze=(t,e)=>{let r=z(t),n=z(e);return {severity:n.severity,options:{...r.options,...n.options}}},Qe=t=>{let e=new Map;for(let r of t)for(let[n,i]of Object.entries(r)){let a=e.get(n);a?e.set(n,Ze(a,i)):e.set(n,z(i));}return e},et=(t,e)=>{let r=new Map(t);for(let[n,i]of Object.entries(e)){let a=r.get(n);a?r.set(n,Ze(a,i)):r.set(n,z(i));}return r};var Dn=async(t,e=process.cwd())=>{common.time("rule-resolution"),common.debug("loader","Starting rule resolution");try{common.debug("loader",`Resolving extends: ${JSON.stringify(t.extends)}`);let r=await Te(t.extends,e);if(!r.ok)return common.timeEnd("rule-resolution"),r;let n=r.data;common.debug("loader",`Loaded ${n.length} preset(s)`);let i=n.map(d=>d.rules);common.debug("loader","Merging preset rules");let a=Qe(i);common.debug("loader",`Merged rules from presets: ${a.size} rules`),common.debug("loader","Applying user config rules");let l=et(a,t.rules||{});common.debug("loader",`Final merged rules: ${l.size} rules`);let s=new Map,p=0,c=0,o=0;for(let[d,y]of l){if(!Ge(d)){common.debug("loader",`Warning: Unknown rule "${d}" (will be skipped)`),o++;continue}let g=Ke(d),h={name:d,severity:y.severity,options:y.options||{},metadata:g};s.set(d,h),Je(y)?p++:c++;}let f=common.timeEnd("rule-resolution");common.debug("loader","Rule resolution complete:"),common.debug("loader",` Total rules: ${s.size}`),common.debug("loader",` Enabled: ${p}`),common.debug("loader",` Disabled: ${c}`),common.debug("loader",` Unknown (skipped): ${o}`),common.debug("loader",` Resolution time: ${f.toFixed(1)}ms`);let m={rules:s,metadata:{totalRules:s.size,enabledRules:p,disabledRules:c,presetsLoaded:n.map(d=>d.name),resolutionTime:f}};return common.Ok(m)}catch(r){return common.timeEnd("rule-resolution"),common.Err(Error(`Rule resolution failed: ${r.message}`))}},Ln=t=>{let e=new Map;for(let[r,n]of t)n.severity!=="off"&&e.set(r,n);return e},$n=t=>{let e=new Map;for(let r of t.values()){let n=r.metadata.category,i=e.get(n)||[];i.push(r),e.set(n,i);}return e},Bn=t=>{let e=new Map;for(let r of t.values()){let n=r.metadata.dependencyType,i=e.get(n)||[];i.push(r),e.set(n,i);}return e};var er;function R(t){let e=t;for(;e;){let r=e.type;if(r==="ChainExpression"||r==="TSNonNullExpression"||r==="TSAsExpression"||r==="ParenthesizedExpression"||r==="TSInstantiationExpression"||r==="TSSatisfiesExpression"){e=e.expression;continue}break}return e??null}function U(t){let e=t?.type;return e==="MemberExpression"||e==="StaticMemberExpression"||e==="OptionalMemberExpression"}function F(t){if(!t)return "";let e=t.property;return e?t.computed?e.type==="Literal"&&typeof e.value=="string"?e.value:"":e.name??"":""}function _n(t){return t?t.start??t.span?.start??0:0}function Un(t,e){let r=R(t);return !!r&&(r.type==="Identifier"?(r.name??"")===e:!!U(r)&&F(r)===e)}function Ts(t){let e=R(t);if(!e||e.type!=="CallExpression")return "";let r=R(e.callee);return r?.type==="Identifier"?r.name??"":U(r)?F(r):""}function ks(t){let e=R(t?.callee);return !!U(e)&&F(e)==="subscribe"}function tr(t,e){if(!t||t.type!=="ObjectExpression")return null;let r=t.properties;if(!Array.isArray(r))return null;for(let n of r){if(!n||n.type!=="Property")continue;let i=n.key;if((i?.type==="Identifier"?i.name:i?.type==="Literal"&&typeof i.value=="string"?i.value:"")===e)return n}return null}function Fn(t){let e=R(t);return !!(e&&e.type==="Literal"&&e.value===true)}function qn(t){let e=R(t);return !e||e.type==="Literal"&&e.value===null||e.type==="Identifier"&&e.name==="undefined"}var zn=new Set(["parent","span","loc","range","start","end","type"]);function*Hn(t){if(t&&typeof t=="object"){if(Array.isArray(t)){for(let e of t)e&&typeof e=="object"&&(yield e);return}for(let e in t){if(zn.has(e))continue;let r=t[e];if(r)if(Array.isArray(r))for(let n of r)n&&typeof n=="object"&&(yield n);else typeof r=="object"&&r.type&&(yield r);}}}function Vn(t){if(!t)return false;if(t.kind==="constructor")return true;let e=t.key;return e?.type==="Identifier"&&e.name==="constructor"||e?.type==="Literal"&&e.value==="constructor"}function Wn(t){if(!t)return false;let e=t.type;return e==="MethodDefinition"||e==="TSAbstractMethodDefinition"||e==="ClassMethod"||e==="MethodDeclaration"}function js(t){for(let e of t)if(e&&Wn(e)&&Vn(e))return e;return null}function Ds(t){return t?(t.value??t)?.body??null:null}function Ls(t){let e=t?.key;return e?e.type==="Identifier"?e.name??"<method>":e.type==="Literal"&&typeof e.value=="string"?e.value:"<method>":"<method>"}function $s(t){if(!t)return [];let e=t.body;if(Array.isArray(e))return e;if(e&&typeof e=="object"&&"body"in e){let r=e.body;if(Array.isArray(r))return r}return []}function Xn(t){let e=R(t);if(!e||e.type!=="CallExpression")return false;let r=e.arguments;if(!Array.isArray(r)||r.length<2)return false;let n=R(r[1]);if(!n||n.type!=="ObjectExpression")return false;let i=tr(n,"injector");if(i&&!qn(i.value))return true;let a=tr(n,"manualCleanup");return !!(a&&Fn(a.value))}function Bs(t){if(!t)return [];let e=[],r=[t];for(;r.length;){let n=R(r.pop());if(n)for(let i of(n.type==="CallExpression"&&Un(n.callee,"effect")&&!Xn(n)&&e.push(n),Hn(n)))r.push(i);}return e}function _s(t){let e=t?.arguments;if(!Array.isArray(e)||e.length===0)return null;let r=R(e[0]);return r&&(r.type==="ArrowFunctionExpression"||r.type==="FunctionExpression")?r:null}function Us(t){if(!t)return null;let e=t.body;return e?R(e):null}function Fs(t,e){let r=new Set([e]);if(typeof t!="string"||t.length===0)return r;for(let n of [/import\s*\{\s*([^}]+)\s*\}\s*from\s*['"]rxjs['"]/g,/import\s*\{\s*([^}]+)\s*\}\s*from\s*['"]rxjs\/[^'"]+['"]/g]){let i;for(;(i=n.exec(t))!==null;)for(let a of (i[1]??"").split(",").map(l=>l.trim()).filter(Boolean)){let l=RegExp(`^${e}(?:\\s+as\\s+([A-Za-z_$][\\w$]*))?$`).exec(a);l&&r.add(l[1]??e);}}return r}function qs(t,e){let r=new Map([...e].map(i=>[i,new Set([i])]));if(typeof t!="string"||t.length===0)return r;let n=new Map([...e].map(i=>[i,RegExp(`^${i}(?:\\s+as\\s+([A-Za-z_$][\\w$]*))?$`)]));for(let i of [/import\s*\{\s*([^}]+)\s*\}\s*from\s*['"]rxjs['"]/g,/import\s*\{\s*([^}]+)\s*\}\s*from\s*['"]rxjs\/[^'"]+['"]/g]){let a;for(;(a=i.exec(t))!==null;)for(let l of (a[1]??"").split(",").map(s=>s.trim()).filter(Boolean))for(let[s,p]of n){let c=p.exec(l);c&&r.get(s).add(c[1]??s);}}return r}var Gn=new Set(["takeUntilDestroyed","takeUntil","take","first","takeWhile"]);function Kn(t){let e=R(t);if(!e)return "";if(e.type==="Identifier")return e.name??"";if(e.type==="CallExpression"){let r=R(e.callee);return r?.type==="Identifier"?r.name??"":U(r)?F(r):""}return U(e)?F(e):""}function Yn(t){let e=t.arguments;if(!Array.isArray(e))return false;for(let r of e)if(Gn.has(Kn(r)))return true;return false}function zs(t){let e=t;for(;e;){let r=R(e);if(!r||r.type!=="CallExpression")break;let n=R(r.callee);if(!U(n))break;if(F(n)==="pipe"&&Yn(r))return true;e=n?.object;}return false}function Hs(t,e){let r=t.template?.templateStartOffset;return typeof r=="number"&&Number.isFinite(r)?e+r:e}var Jn=new Set(["get","post","put","patch","delete","head","options","jsonp","request"]),Zn=new Set(["http","httpClient","_http","_httpClient"]),Qn=["get","fetch","load","save","create","update","delete","remove","submit","send","post","put","patch","upload","download"];function Vs(t){let e=t;for(;e;){let r=R(e);if(!r||r.type!=="CallExpression")break;let n=R(r.callee);if(!U(n))break;if(F(n)!=="pipe")return r;e=n?.object;}return null}function Ws(t){if(!t)return false;let e=R(t);if(!e||e.type!=="CallExpression")return false;let r=R(e.callee);if(!U(r))return false;let n=F(r);if(!n)return false;let i=R(r?.object),a=Array.isArray(e.arguments)?e.arguments:[];if(Jn.has(n)&&U(i)){let l=F(i);if(Zn.has(l))return true}if(a.length>0){let l=n.toLowerCase();for(let s of Qn)if(l.startsWith(s)&&n.length>s.length)return true}return false}function Xs(t){if(!t)return [];let e=t.params;if(Array.isArray(e))return e;if(e&&typeof e=="object"){if("items"in e&&Array.isArray(e.items))return e.items;if("elements"in e&&Array.isArray(e.elements))return e.elements}return []}function ei(t){let e=R(t);if(!e)return "";if(e.type==="Identifier")return e.name??"";if(e.type==="TSParameterProperty")return ei(e.parameter);if(e.type==="AssignmentPattern"){let r=R(e.left);return r?.type==="Identifier"?r.name??"":""}if(e.type==="RestElement"){let r=R(e.argument);return r?.type==="Identifier"?r.name??"":""}return ""}function ti(t){let e=R(t);if(!e)return "";if(e.type==="TSParameterProperty")return ti(e.parameter);let r=e.typeAnnotation,n=R(r?.typeAnnotation??r);if(!n)return "";if(n.type==="TSTypeReference"||n.type==="TypeReference"){let i=n.typeName??n.name;if(i&&typeof i=="object"){if(i.type==="Identifier")return i.name??"";if(i.type==="TSQualifiedName")return i.right?.name??""}if(typeof i=="string")return i}return ""}var rr=false;function nr(){if(!rr){try{er=nt("typescript");}catch{}rr=true;}return er}function Gs(t,e){if(!t||!e.typeChecker)return;let r=nr();if(r)try{!e.sourceFile&&e.fileContent&&(e.sourceFile=r.createSourceFile(e.filePath,e.fileContent,r.ScriptTarget.Latest,!0));let n=e.sourceFile;if(!n)return;let i=(function(a,l,s){if(l<a.getStart()||l>=a.getEnd())return;let p=a;for(;;){let c=s.forEachChild(p,o=>{if(l>=o.getStart()&&l<o.getEnd())return o});if(!c)break;p=c;}return p})(n,_n(t),r);return i?e.typeChecker.getSymbolAtLocation(i):void 0}catch{return}}var ri=["Service","Facade","Store","Client","Repository","Adapter","Controller","Provider","Registry","Logger","Router","Injector","Handler","Interceptor","Guard","Resolver","Validator"];function Ks(t){if(!t)return false;let e=nr();if(!e)return false;try{let r=t.getName();if(ri.some(a=>r.endsWith(a)))return !0;let n=t.getDeclarations();if(!n||n.length===0)return !1;let i=n[0];if(!e.canHaveDecorators(i))return !1;for(let a of e.getDecorators(i)??[]){let l=a.expression;if(e.isCallExpression(l)&&e.isIdentifier(l.expression)&&l.expression.text==="Injectable")return !0}return !1}catch{return false}}var Js="@ngcompass/rules";Object.defineProperty(exports,"RuleContextFactory",{enumerable:true,get:function(){return engine.RuleContextFactory}});exports.CODE_EXAMPLES=pa;exports.RECOMMENDATIONS=Kt;exports.RuleRegistry=Pe;exports.VALID_TEARDOWN_OPERATORS=Gn;exports.applyOverrides=et;exports.builtinPresets=Ne;exports.childNodes=Hn;exports.collectAllRxjsAliases=qs;exports.collectRxjsAliases=Fs;exports.componentNoManualDetectChangesRule=ft;exports.executeBatchedNewEngineRules=Nn;exports.findEffectCalls=Bs;exports.findObservableSourceCall=Vs;exports.getAllRuleNames=An;exports.getBuiltinPreset=vn;exports.getCallbackArg=_s;exports.getCalleeName=Ts;exports.getClassBody=$s;exports.getConstructorMember=js;exports.getEnabledRules=Ln;exports.getFunctionBody=Us;exports.getGlobalRegistry=$;exports.getMethodBody=Ds;exports.getMethodName=Ls;exports.getNodeStart=_n;exports.getObjectProperty=tr;exports.getOperatorNameFromPipeArg=Kn;exports.getParamIdentifierName=ei;exports.getParamTypeName=ti;exports.getParamsArray=Xs;exports.getPresetsForRule=Xe;exports.getRuleListEntries=Cn;exports.getRuleMetadata=Ke;exports.getRulesByCategory=$n;exports.getRulesByDependencyType=Bn;exports.getStaticPropertyName=F;exports.getTemplateAbsoluteOffset=Hs;exports.getTsSymbolAtNode=Gs;exports.hasManualTeardownInNgOnDestroy=ze;exports.hasTeardownInPipeCall=Yn;exports.hasTeardownInReceiverChain=zs;exports.isAllowedEffectCall=Xn;exports.isBuiltinPreset=bn;exports.isCalleeNamed=Un;exports.isConstructorMethod=Vn;exports.isKnownRule=Ge;exports.isLikelyAngularInjectableSymbol=Ks;exports.isLikelyHttpObservable=Ws;exports.isLiteralNullOrUndefined=qn;exports.isLiteralTrue=Fn;exports.isMemberExpressionLike=U;exports.isMethodDefinition=Wn;exports.isNewEngineRule=Rn;exports.isRuleEnabled=Je;exports.isSubscribeCall=ks;exports.loadPreset=Qt;exports.mergeRuleConfig=Ze;exports.mergeRulesConfigs=Qe;exports.noBypassSanitizationRule=Ct;exports.noDocumentAccessRule=Rt;exports.normalizeAllRules=On;exports.normalizeRuleConfig=z;exports.preferAfterRenderOverAfterViewInitRule=Nt;exports.preferInjectRule=Bt;exports.preferOnPushRule=ht;exports.registerAllBuiltinRules=Mn;exports.resetGlobalRegistry=En;exports.resolveExtendsChain=Te;exports.resolveRules=Dn;exports.ruleRegistry=Sn;exports.rules=Js;exports.rxjsAvoidSubjectRule=Ot;exports.rxjsNoNestedSubscribeRule=yt;exports.rxjsNoSubscribeInComponentRule=Pt;exports.rxjsPreferToSignalRule=kt;exports.rxjsRequireTakeUntilDestroyedRule=Mt;exports.signalAvoidUntrackedRule=$t;exports.signalEffectDestroyScopedRule=dt;exports.signalNoSideEffectsInComputedRule=mt;exports.signalPreferComputedRule=Dt;exports.signalPreferInputSignalRule=_t;exports.signalPreferModelRule=zt;exports.signalPreferOutputFunctionRule=Ut;exports.specNoFocusedTestRule=Gt;exports.templateNoArrayLiteralBindingRule=St;exports.templateNoAsyncPipeDuplicationRule=Wt;exports.templateNoCallExpressionRule=bt;exports.templateNoObjectLiteralBindingRule=At;exports.templateNoUnsafeBindingsRule=wt;exports.templatePreferControlFlowRule=Ht;exports.templateTrackByRequiredRule=Et;exports.toSignalRequireInitialValueRule=jt;exports.unwrapNode=R;//# sourceMappingURL=index.cjs.map
432
+ //# sourceMappingURL=index.cjs.map