Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,28 @@ on: [push]
jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- angular: 21
typescript: ~5.9.0
- angular: 22
typescript: ~6.0.0
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 22
node-version: 22.22.3
cache: npm
- name: Install dependencies
run: npm ci
- name: Install compatibility dependencies
run: npm install --no-save --package-lock=false @angular-eslint/template-parser@${{ matrix.angular }} @angular-eslint/test-utils@${{ matrix.angular }} @angular/common@${{ matrix.angular }} @angular/compiler@${{ matrix.angular }} @angular/core@${{ matrix.angular }} @angular/forms@${{ matrix.angular }} @angular/platform-browser@${{ matrix.angular }} @angular/router@${{ matrix.angular }} @ionic/core@9 typescript@${{ matrix.typescript }}
- name: Install Ionic Angular peer fixture
run: npm install --prefix /tmp/ionic-angular-${{ matrix.angular }} --no-save @ionic/angular@9 @angular/common@${{ matrix.angular }} @angular/compiler@${{ matrix.angular }} @angular/core@${{ matrix.angular }} @angular/forms@${{ matrix.angular }} @angular/platform-browser@${{ matrix.angular }} @angular/router@${{ matrix.angular }} rxjs@7
- name: Verify compatibility dependency versions
run: node scripts/check-compatibility-versions.mjs ${{ matrix.angular }} ${{ matrix.typescript }} /tmp/ionic-angular-${{ matrix.angular }}
- name: Lint
run: npm run lint
- name: Test
Expand Down
18 changes: 10 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,18 @@ Install the plugin as a development dependency:
npm install --save-dev @rdlabo/eslint-plugin-rules
```

The package root provides Angular and Ionic rules. Install `@angular-eslint/template-parser` and `@ionic/core` when you enable those rules. Framework-independent TypeScript projects can use the `/typescript` entry point without loading Angular or Ionic.
The package root provides Angular and Ionic rules. Install `@angular-eslint/template-parser`, `@ionic/angular`, and `@ionic/core` when you enable those rules. Framework-independent TypeScript projects can use the `/typescript` entry point without loading Angular or Ionic.

## Requirements

| Package | Supported version |
| --------------------------------- | --------------------------------- |
| Node.js | 20 or later |
| ESLint | 9 or later |
| `@typescript-eslint/utils` | 8.33 or later, before 9 |
| `@angular-eslint/template-parser` | 21.x when template rules are used |
| `@ionic/core` | 8.x when Ionic rules are used |
| Package | Supported version |
| --------------------------------- | ----------------------------- |
| Node.js | 20 or later |
| ESLint | 9 or later |
| `@typescript-eslint/utils` | 8.33 or later, before 9 |
| `@angular-eslint/template-parser` | 21.x or 22.x |
| `@ionic/angular` | 9.x when Ionic rules are used |
| `@ionic/core` | 9.x when Ionic rules are used |

## Choose an entry point

Expand All @@ -38,6 +39,7 @@ Continue to [Configuration](./docs/configuration.md) to enable the recommended p
## Documentation

- [Configuration](./docs/configuration.md)
- [Migration guide](./docs/migration.md)
- [Rules](./docs/rules.md)

<!-- rdlabo-docs-omit -->
Expand Down
4 changes: 4 additions & 0 deletions docs/configuration.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
## Angular and Ionic

Plugin 22 supports Angular and Angular ESLint 21–22 with Ionic Framework 9. When upgrading from plugin 21, review the [migration guide](./migration.md) before enabling the updated recommended preset.

Register the plugin, spread its recommended configs at the top level, then add the standard Angular and TypeScript configs for your project.

```js
Expand Down Expand Up @@ -61,4 +63,6 @@ Typed linting is required for the full Promise and RxJS checks in `restrict-try-

The preset enables the common Signal, component boundary, lifecycle, overlay, readonly, and try-block rules for TypeScript. Its HTML config enables Ionic attribute checking, denied overlay elements, and double-action prevention.

The TypeScript preset includes `prefer-ionic-standalone`, which requires Ionic 9 root imports and rejects `IonicModule` and NgModule-based lazy imports.

`deny-constructor-di` is deprecated and is not in the preset. Prefer Angular's `inject()` migration.
58 changes: 58 additions & 0 deletions docs/migration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Migration guide

## 21.x to 22.x

Version 22 targets Angular 21 and 22 with Ionic Framework 9. Ionic 8 applications must remain on version 21 of this plugin.

### Dependencies

Commit your application changes first, then run Ionic's official [`@ionic/migrate`](https://www.npmjs.com/package/@ionic/migrate) tool from the application root:

```sh
npx @ionic/migrate --dry-run
npx @ionic/migrate
```

The migrator detects the installed Ionic major, updates `@ionic/angular` and `@ionic/core` together, applies safe v8-to-v9 changes, and prints a checklist for changes that require manual decisions. Review and test its diff before continuing. Version 22 of this plugin supports Angular and Angular ESLint 21 through 22.

### Ionic Angular imports

Replace the removed `deny-import-from-ionic-module` rule with `prefer-ionic-standalone`:

```diff
- '@rdlabo/rules/deny-import-from-ionic-module': 'error'
+ '@rdlabo/rules/prefer-ionic-standalone': 'error'
```

For Angular applications, the official migrator moves existing NgModule imports from `@ionic/angular` to `@ionic/angular/lazy` and standalone imports from `@ionic/angular/standalone` to the package root. This preserves the application's current architecture during the framework upgrade.

For example, the migrator performs this safe standalone import rewrite automatically:

```diff
- import { IonButton } from '@ionic/angular/standalone';
+ import { IonButton } from '@ionic/angular';
```

This plugin supports only Ionic 9 standalone applications. The official migrator reports `IonicModule` without an autofix because converting an NgModule application requires architectural decisions. After running it, complete the Angular standalone migration and import Ionic components from the package root. Do not mechanically replace `@ionic/angular/lazy` paths: first convert each NgModule consumer to standalone, then replace `IonicModule` with the specific Ionic components it uses.

The new rule rejects the NgModule-based `@ionic/angular/lazy` entry point and `IonicModule`. Migrate to standalone bootstrap with `provideIonicAngular()` and import standalone Ionic components directly:

```diff
- platformBrowserDynamic().bootstrapModule(AppModule);
+ bootstrapApplication(AppComponent, {
+ providers: [provideIonicAngular(config)],
+ });
```

Import `provideIonicAngular` from `@ionic/angular`. Complete the Angular NgModule-to-standalone migration before removing `IonicModule`; it cannot be replaced safely inside an NgModule with a one-line autofix.

### Boolean autocorrect

Ionic 9 changes `autocorrect` on `ion-input` and `ion-searchbar` from `'on' | 'off'` to `boolean`. The `ionic-attr-type-check` rule now fixes the old string form:

```diff
- <ion-input autocorrect="off"></ion-input>
+ <ion-input [autocorrect]="false"></ion-input>
```

The official Ionic migrator handles this v8-to-v9 change automatically. The rule remains useful for detecting old or newly introduced string values after migration and reads Ionic 9 component types, so it also follows other property type and accepted-value changes exposed by those definitions. Run ESLint with `--fix`, review the resulting template changes, and then run the Angular build and tests before committing.
4 changes: 2 additions & 2 deletions docs/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ The package exposes 18 rules. Rules marked “recommended” are enabled by `rdl
| [`component-property-use-readonly`](./rules/component-property-use-readonly.md) | Require `readonly` on immutable Angular component properties. | Yes | Yes |
| [`deny-constructor-di`](./rules/deny-constructor-di.md) | Disallow constructor dependency injection. Deprecated in favor of `inject()`. | No | No |
| [`deny-element`](./rules/deny-element.md) | Reject configured HTML elements, such as inline Ionic overlays. | No | Yes |
| [`deny-import-from-ionic-module`](./rules/deny-import-from-ionic-module.md) | Replace accidental `@ionic/angular` imports with standalone imports. | Yes | Yes |
| [`deny-overlay-create`](./rules/deny-overlay-create.md) | Disallow direct `.create()` calls on modal and popover controllers. | No | Yes |
| [`deny-soft-private-modifier`](./rules/deny-soft-private-modifier.md) | Replace TypeScript `private` with hard-private `#` fields. | Yes | Yes |
| [`implements-ionic-lifecycle`](./rules/implements-ionic-lifecycle.md) | Require the matching interface for Angular and Ionic lifecycle methods. | Yes | Yes |
Expand All @@ -15,6 +14,7 @@ The package exposes 18 rules. Rules marked “recommended” are enabled by `rdl
| [`no-reactive-forms`](./rules/no-reactive-forms.md) | Disallow Reactive Forms in favor of Angular Signal Forms. | No | No |
| [`no-template-driven-forms`](./rules/no-template-driven-forms.md) | Disallow template-driven forms except configured interoperability elements. | No | No |
| [`prefer-disable-handler`](./rules/prefer-disable-handler.md) | Wrap configured event handlers to prevent duplicate async actions. | No | Yes |
| [`prefer-ionic-standalone`](./rules/prefer-ionic-standalone.md) | Prefer Ionic 9 standalone imports and disallow `IonicModule`. | Yes | Yes |
| [`prefer-modal-launcher`](./rules/prefer-modal-launcher.md) | Restrict `presentModal` calls to `launch*` functions. | No | Yes |
| [`require-viewmodel`](./rules/require-viewmodel.md) | Enforce component ownership and the `ViewModelStore` boundary. | No | Yes |
| [`restrict-try-block`](./rules/restrict-try-block.md) | Keep `try` blocks small and exclude Promise, RxJS, and Signal contexts by policy. | No | Yes |
Expand All @@ -23,7 +23,7 @@ The package exposes 18 rules. Rules marked “recommended” are enabled by `rdl

## Rule documentation

Each rule page in this documentation contains options and correct/incorrect examples. The table above reflects v21.3.0.
Each rule page in this documentation contains options and correct/incorrect examples.

## Typed rules

Expand Down
31 changes: 0 additions & 31 deletions docs/rules/deny-import-from-ionic-module.md

This file was deleted.

37 changes: 37 additions & 0 deletions docs/rules/prefer-ionic-standalone.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# @rdlabo/rules/prefer-ionic-standalone

> Prefer the Ionic 9 standalone API and disallow IonicModule and obsolete or NgModule-based entry points.
>
> - ⭐️ This rule is included in `plugin:@rdlabo/rules/recommended` preset.
> - ✒️ The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule.

Ionic 9 exports standalone Angular components from `@ionic/angular`. This rule keeps applications on that API surface by rejecting the obsolete `@ionic/angular/standalone` entry point, the NgModule-based `@ionic/angular/lazy` entry point, and `IonicModule` itself.

## Examples

### Incorrect

```ts
import { IonButton } from '@ionic/angular/standalone';
import { IonInput } from '@ionic/angular/lazy';
import { IonicModule } from '@ionic/angular';
```

### Correct

```ts
import { IonButton, IonInput, ModalController, provideIonicAngular } from '@ionic/angular';
```

Named imports and named re-exports from `/standalone` and `/lazy` are auto-fixed to `@ionic/angular`, preserving the original quote style. Side-effect imports, namespace imports, and `export *` declarations are reported without a fix because changing their entry point may change runtime behavior. `IonicModule` is also reported without a fix because replacing `IonicModule.forRoot()` and NgModule metadata requires application-level changes.

Imports and re-exports are checked, including `Ionic.IonicModule` access through a namespace import.

## Options

This rule has no options. Configure its severity as `warn` or `error` in ESLint configuration.

## Implementation

- [Rule source](../../src/rules/prefer-ionic-standalone.ts)
- [Test source](../../tests/rules/prefer-ionic-standalone.ts)
Loading