Back to skills
extension
Category: Development & EngineeringNo API key required

migration-lint

Comprehensive Angular migration compliance checker with auto-fix capabilities. Use when reviewing migrated code for DDD architecture, Angular 20 syntax, and UI pattern compliance.

personAuthor: jakexiaohubgithub

Migration Lint Command

Review and fix migrated Angular code for compliance. This command reads ALL migration guidelines and performs a comprehensive checklist review.

Arguments

  • $ARGUMENTS - Target path to review (e.g., libs/mxsecurity/connection-management-page)

Workflow

Step 1: Load ALL Migration Guidelines

IMPORTANT: Read the core SKILL.md first, then reference documents as needed from ``.

Core: one-ui-migration skill

| # | Topic | File | Category | |---|-------|------|----------| | 1 | Migration Context | rules/reference/migration-context.md | Foundation | | 2 | Angular 20 Syntax | rules/reference/angular-20-syntax.md | Foundation | | 3 | DDD Architecture | rules/reference/ddd-architecture.md | Architecture | | 4 | State Management | rules/reference/state-management.md | Architecture | | 5 | Authentication | rules/tools/auth.md | Architecture | | 6 | Migration Checklist | rules/reference/migration-checklist.md | Foundation | | 7 | Pitfalls Index | rules/reference/pitfalls/index.md | Pitfalls | | 7a | Pitfalls: Translation & Layout | rules/reference/pitfalls/translation-layout.md | Pitfalls | | 7b | Pitfalls: DDD Violations | rules/reference/pitfalls/ddd-violations.md | Pitfalls | | 7c | Pitfalls: Angular Syntax | rules/reference/pitfalls/angular-syntax.md | Pitfalls | | 7d | Pitfalls: Forms & Services | rules/reference/pitfalls/forms-services.md | Pitfalls | | 8 | Validators | rules/tools/forms/validators.md | Forms | | 9 | Error Handling | rules/tools/forms/error-handling.md | Forms | | 10 | Form Patterns | rules/tools/forms/patterns.md | Forms | | 11 | Page Layout | rules/tools/ui/page-layout.md | UI Patterns | | 12a | Forms | rules/tools/ui/forms.md | UI Patterns | | 12b | Buttons | rules/tools/ui/buttons.md | UI Patterns | | 12c | Components | rules/tools/ui/components.md | UI Patterns | | 13 | Dialogs | rules/tools/ui/dialogs.md | UI Patterns | | 14 | Table Basics | rules/tools/tables/basics.md | Tables | | 15 | Table Columns | rules/tools/tables/columns.md | Tables | | 16 | Table Advanced | rules/tools/tables/advanced.md | Tables | | 17 | API Type Definitions | rules/reference/api-types.md | API | | 18 | Shared Stores | rules/reference/shared-stores.md | State |

Step 2: Read Target Files

Read all .ts, .html, and .scss files in the target path:

  • {target}/domain/src/lib/**/*.ts
  • {target}/features/src/lib/**/*.ts
  • {target}/features/src/lib/**/*.html
  • {target}/features/src/lib/**/*.scss
  • {target}/ui/src/lib/**/*.ts
  • {target}/ui/src/lib/**/*.html
  • {target}/ui/src/lib/**/*.scss
  • {target}/shell/src/lib/**/*.ts

Step 3: Comprehensive Checklist Review

Check EVERY item from EVERY migration file:


Checklist by Category

Category 1: Foundation & Pitfalls

migration-context.md

  • [ ] Project follows one-ui monorepo structure
  • [ ] Uses correct scope tags (scope:mxsecurity)
  • [ ] Library types match DDD pattern (domain, features, ui, shell)

angular-syntax.md

  • [ ] Uses @if instead of *ngIf
  • [ ] Uses @for (item of items; track item.id) instead of *ngFor
  • [ ] Uses @switch instead of ngSwitch
  • [ ] Uses inject() instead of constructor injection
  • [ ] Uses input() instead of @Input()
  • [ ] Uses output() instead of @Output()
  • [ ] Uses viewChild() / viewChildren() instead of @ViewChild() / @ViewChildren()
  • [ ] Uses contentChild() / contentChildren() instead of @ContentChild() / @ContentChildren()
  • [ ] Uses model() for two-way binding where appropriate
  • [ ] Components are standalone (default in Angular 20)
  • [ ] Uses ChangeDetectionStrategy.OnPush

migration-checklist.md

  • [ ] Domain layer contains only business logic, models, services, state
  • [ ] Features layer contains smart components with store injection
  • [ ] UI layer contains presentational components with input/output ONLY (NO store injection)
  • [ ] Shell layer contains routing configuration
  • [ ] Barrel exports (index.ts) are properly configured

pitfalls/translation-layout.md

  • [ ] Translation keys match source exactly (no new keys created)
  • [ ] Form layout matches source (row groupings preserved)
  • [ ] No padding in page component styles
  • [ ] Page uses gl-page-content class (not ng-container)
  • [ ] Table toolbar button order: Refresh → Create/Delete (only if old code has refresh)

pitfalls/ddd-violations.md

  • [ ] No form templates in features layer (extract to UI)
  • [ ] No store injection in UI components
  • [ ] Dialogs are in features layer (not UI)
  • [ ] No business logic in features (move to domain)
  • [ ] No HTTP calls in UI components

pitfalls/angular-syntax.md

  • [ ] No mixing old/new control flow (*ngIf with @for)
  • [ ] All @for loops have track
  • [ ] No NgModule patterns (use standalone)
  • [ ] langChanged() called at field level (not in computed)
  • [ ] Uses inject() instead of constructor injection

pitfalls/forms-services.md

  • [ ] mutationMethod/queryMethod imported from @one-ui/mxsecurity/shared/domain (NOT shared/util)
  • [ ] No unnecessary .def.ts files for constants
  • [ ] Uses MxSnackbarService (not MatSnackBar)
  • [ ] Uses controls.xxx (not .get('xxx'))
  • [ ] Form valueChanges uses toSignal() in effects
  • [ ] Uses EMPTY_DASH constant (not utils.emptyDash)
  • [ ] Uses oneUiNumberOnly (not appNumberOnly)
  • [ ] Uses mx-password-input for password fields
  • [ ] Uses mx-key-value for readonly displays
  • [ ] No TRANSLOCO_SCOPE provider (use global translation keys)
  • [ ] No endpoint constants (#ENDPOINTS = {}) - inline URLs directly in API methods

Category 2: Architecture

ddd-architecture.md

  • [ ] Domain layer has correct structure
  • [ ] Features layer has correct structure
  • [ ] UI layer has correct structure
  • [ ] Shell layer has correct structure
  • [ ] Dependencies flow correctly (UI → Domain, Features → UI/Domain)
  • [ ] No UI components importing from features
  • [ ] Shared domain utilities used where appropriate

state-management.md

  • [ ] Uses NgRx SignalStore for state management
  • [ ] Store follows correct naming convention (XxxStore)
  • [ ] Uses withEntities() for entity management
  • [ ] Uses withRequestStatus() for loading states
  • [ ] Uses withDataService() for API integration
  • [ ] Effects handle success/error properly
  • [ ] Uses patchState() correctly
  • [ ] Store is provided in correct scope

authentication.md

  • [ ] Uses AuthStore for authentication state
  • [ ] Permission checks use correct patterns
  • [ ] Protected routes have guards
  • [ ] API calls include proper authentication headers

Category 3: Forms (forms/)

validators.md

  • [ ] Uses OneValidators.required (not Validators.required)
  • [ ] Uses OneValidators.email (not Validators.email)
  • [ ] Uses OneValidators.minLength(n) (not Validators.minLength)
  • [ ] Uses OneValidators.maxLength(n) (not Validators.maxLength)
  • [ ] Uses OneValidators.min(n) (not Validators.min)
  • [ ] Uses OneValidators.max(n) (not Validators.max)
  • [ ] Uses OneValidators.pattern(x) (not Validators.pattern)
  • [ ] Custom validators follow correct patterns
  • [ ] Cross-field validators implemented correctly
  • [ ] Import from @one-ui/mxsecurity/shared/domain

error-handling.md

  • [ ] Form errors displayed using <mat-error>
  • [ ] Error messages use translation keys
  • [ ] Async validators handle errors
  • [ ] API errors displayed to user
  • [ ] Uses MxSnackbarService for notifications

patterns.md

  • [ ] Uses NonNullableFormBuilder (not FormBuilder)
  • [ ] Form controls accessed via controls.xxx (not .get('xxx'))
  • [ ] getRawValue() used for form submission
  • [ ] Dynamic forms use FormArray correctly
  • [ ] Form reset/patch follows patterns
  • [ ] Forms in UI layer use input()/output() for data flow

Category 4: UI Patterns (ui/)

ui/page-layout.md

  • [ ] Root wrapper has class="gl-page-content"
  • [ ] Uses <one-ui-breadcrumb /> at top
  • [ ] Uses <mx-page-title [title]="t('...')"> for page title
  • [ ] Content wrapped in class="content-wrapper" (NOT mat-card)
  • [ ] Uses *transloco="let t" pattern
  • [ ] Section titles use class="gl-title-md"

buttons.md

  • [ ] Submit button uses mat-flat-button color="primary"
  • [ ] Cancel button uses mat-button
  • [ ] Toolbar buttons use mat-stroked-button
  • [ ] NO mat-raised-button (use mat-flat-button)
  • [ ] Loading state uses [mxButtonIsLoading] directive
  • [ ] Disabled state properly handles noPermission()

forms.md

  • [ ] Single form field NOT wrapped in <div class="form-row">
  • [ ] Multiple fields on same row use <div class="form-row">

dialogs.md

  • [ ] Uses shared dialog config (smallDialogConfig, mediumDialogConfig, largeDialogConfig)
  • [ ] Has viewContainerRef: this.#viewContainerRef when dialog uses store
  • [ ] ConfirmDialogComponent result checked as boolean (NOT result?.confirm)
  • [ ] Dialog data typed properly
  • [ ] Dialog returns correct type
  • [ ] afterClosed() subscription cleaned up properly

Category 5: Tables (tables/)

basics.md

  • [ ] Uses <mx-table> component
  • [ ] Column definitions use MxColumnDef[]
  • [ ] Has data-testid attributes
  • [ ] Loading state shows skeleton
  • [ ] Empty state handled properly

columns.md

  • [ ] Column types match data
  • [ ] Custom templates import MatTableModule and MatSortModule
  • [ ] Link columns use proper navigation
  • [ ] Status columns use proper styling
  • [ ] Action columns positioned correctly

advanced.md

  • [ ] Selection implemented correctly (if applicable)
  • [ ] Sorting configured properly
  • [ ] Pagination implemented (if applicable)
  • [ ] Filtering works correctly (if applicable)
  • [ ] Toolbar buttons use mat-stroked-button

Category 6: API & Services

api-types.md

  • [ ] API types defined in domain layer
  • [ ] Request/Response types properly typed
  • [ ] Uses proper HTTP methods
  • [ ] Error responses typed
  • [ ] Enums defined for constants
  • [ ] NO endpoint constants (#ENDPOINTS = {...}) - inline URL strings directly in methods

shared-stores.md

  • [ ] Uses shared stores where appropriate (e.g., TimeZoneStore)
  • [ ] Shared store initialization handled
  • [ ] Cross-feature state accessed via shared stores
  • [ ] No duplicate state management

Category 7: Additional Checks

Translation (Transloco)

  • [ ] Uses *transloco="let t" or @let t = translocoT
  • [ ] NO translate pipe
  • [ ] Translation keys follow naming convention
  • [ ] Parameters passed correctly to t()

Directives

  • [ ] oneUiNumberOnly (not appNumberOnly)
  • [ ] Custom directives use one-ui prefix

Services

  • [ ] MxSnackbarService (not MatSnackBar)
  • [ ] Snackbar: snackBar.open(msg) (not snackBar.open(msg, '', { duration: 3000 }))

Constants

  • [ ] EMPTY_DASH from @one-ui/mxsecurity/shared/domain (not utils.emptyDash)

Imports

  • [ ] No circular imports
  • [ ] Imports from correct library paths
  • [ ] No relative imports crossing library boundaries

Password Fields

  • [ ] Uses <mx-password-input> for password fields

Readonly Display

  • [ ] Uses <mx-key-value> for readonly key-value displays

Step 4: Fix Issues

Auto-fix the following issues:

| Issue | Fix | |-------|-----| | mat-raised-button | → mat-flat-button | | *ngIf | → @if | | *ngFor | → @for (item of items; track item.id) | | Validators.required | → OneValidators.required | | Validators.email | → OneValidators.email | | Validators.minLength(n) | → OneValidators.minLength(n) | | Validators.maxLength(n) | → OneValidators.maxLength(n) | | .get('xxx') | → controls.xxx | | class="section-title" | → class="gl-title-md" | | appNumberOnly | → oneUiNumberOnly | | this.utils.emptyDash | → EMPTY_DASH | | readonly #ENDPOINTS = { | → Remove constant, inline URLs directly in each method | | from '@one-ui/mxsecurity/shared/util' (for mutationMethod/queryMethod) | → from '@one-ui/mxsecurity/shared/domain' |

Step 5: Generate Report

Output a compliance report:

## Migration Lint Report

**Path:** {target_path}
**Date:** {current_date}

### Checklist Summary

| Category | Files Checked | Pass | Fail | N/A |
|----------|---------------|------|------|-----|
| Foundation (01, 02, 06) | 3 | Pass | Fail | - |
| Architecture (03, 04, 05) | 3 | Pass | Fail | - |
| Pitfalls (08, 08a, 08b, 08c, 08d) | 5 | Pass | Fail | - |
| Forms (09a, 09b, 09c) | 3 | Pass | Fail | - |
| UI Patterns (10a, 10b, 10c) | 3 | Pass | Fail | - |
| Tables (11a, 11b, 11c) | 3 | Pass | Fail | - |
| API & Services (12, 13) | 2 | Pass | Fail | - |
| **Total** | **22** | **X** | **Y** | **Z** |

### Issues Fixed

| File | Line | Issue | Fix Applied |
|------|------|-------|-------------|
| component.html | 15 | mat-raised-button | Changed to mat-flat-button |

### Issues Found (Manual Review Needed)

| File | Line | Issue | Recommendation | Guide Ref |
|------|------|-------|----------------|-----------|
| component.ts | 42 | Missing viewContainerRef | Add viewContainerRef to dialog config | dialogs.md |
| store.ts | 20 | Store in UI layer | Move to features layer | ddd-architecture.md |

### Detailed Checklist Results

#### migration-context.md (Pass)
- [x] Project follows one-ui monorepo structure
- [x] Uses correct scope tags

#### angular-syntax.md (Warning)
- [x] Uses @if instead of *ngIf
- [ ] Uses inject() instead of constructor injection (line 45)

... (continue for all files)

### Summary

- **Migration Files Checked:** 22/22
- **Auto-fixed:** X issues
- **Manual review needed:** Y issues
- **Passed checks:** Z items
- **Failed checks:** W items

Quick Reference

Button Types

<!-- Form submit button -->
<button mat-flat-button color="primary" type="submit">Apply</button>

<!-- Table toolbar button -->
<button mat-stroked-button>Create</button>

<!-- Cancel/secondary button -->
<button mat-button type="button">Cancel</button>

Page Layout

<div *transloco="let t" class="gl-page-content">
  <one-ui-breadcrumb />
  <mx-page-title [title]="t('page.title')" />

  <div class="content-wrapper">
    <!-- content here, NO mat-card -->
  </div>
</div>

Form Row

<!-- Single field - NO form-row wrapper -->
<mat-form-field>
  <mat-label>{{ t('label') }}</mat-label>
  <input matInput formControlName="field" />
</mat-form-field>

<!-- Multiple fields on same row - USE form-row wrapper -->
<div class="form-row">
  <mat-form-field>...</mat-form-field>
  <mat-form-field>...</mat-form-field>
</div>

Dialog Config

import { mediumDialogConfig } from '@one-ui/mxsecurity/shared/domain';

this.#dialog.open(MyDialog, {
  ...mediumDialogConfig,
  data: dialogData,
  viewContainerRef: this.#viewContainerRef  // Required if dialog uses store
});

Validators

import { OneValidators } from '@one-ui/mxsecurity/shared/domain';

this.#fb.group({
  name: ['', [OneValidators.required, OneValidators.maxLength(32)]],
  email: ['', [OneValidators.required, OneValidators.email]],
  port: ['', [OneValidators.required, OneValidators.min(1), OneValidators.max(65535)]]
});

ConfirmDialogComponent

// ConfirmDialogComponent returns boolean, NOT { confirm: boolean }
const dialogRef = this.#dialog.open<ConfirmDialogComponent, ConfirmDialogData, boolean>(
  ConfirmDialogComponent,
  {
    ...mediumDialogConfig,
    viewContainerRef: this.#viewContainerRef,
    data: { title: '...', desc: '...' }
  }
);

dialogRef.afterClosed().subscribe((result) => {
  if (result) {  // result is boolean
    this.doSomething();
  }
});

Snackbar Service

// Use MxSnackbarService instead of MatSnackBar
import { MxSnackbarService } from '@moxa/formoxa/mx-snackbar';

readonly #snackBar = inject(MxSnackbarService);

// Simple usage
this.#snackBar.open('Success message');

// Persistent message (no auto-dismiss)
this.#snackBar.open('Saving...', { duration: 0 });

Section Title

<!-- Use gl-title-md class (global style) -->
<h3 class="gl-title-md">{{ t('section.title') }}</h3>

<!-- NOT section-title (requires custom SCSS) -->
<h3 class="section-title">{{ t('section.title') }}</h3>

Form Controls Access

// .get() - no type safety
const value = this.form.get('username')?.value;

// controls.xxx - type safe
const value = this.form.controls.username.value;

// In ValidatorFn - use type assertion
#myValidator(): ValidatorFn {
  return (formGroup: AbstractControl): null => {
    const controls = (formGroup as typeof this.form).controls;
    const fieldValue = controls.fieldName.value;
    return null;
  };
}

Password Input

<!-- Use mx-password-input for password fields -->
<mx-password-input
  formControlName="password"
  [maxlength]="32"
></mx-password-input>

Key-Value Display

<!-- Use mx-key-value for readonly displays -->
<mx-key-value [key]="t('label')" [value]="displayValue"></mx-key-value>

UI Component Pattern

// UI components use input()/output() ONLY - NO store injection
export class MyUiComponent {
  readonly data = input<MyData>();
  readonly isLoading = input<boolean>(false);

  readonly save = output<MyData>();
  readonly cancel = output<void>();
}

Features Component Pattern

// Features components can inject stores
export class MyFeatureComponent {
  readonly #store = inject(MyStore);

  readonly data = this.#store.data;
  readonly isLoading = this.#store.isLoading;
}