diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
index fbf3e0f..6f6a1fe 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -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
diff --git a/README.md b/README.md
index 10eccbd..98b42f8 100644
--- a/README.md
+++ b/README.md
@@ -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
@@ -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)
diff --git a/docs/configuration.md b/docs/configuration.md
index 1b6b412..7b56dfc 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -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
@@ -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.
diff --git a/docs/migration.md b/docs/migration.md
new file mode 100644
index 0000000..2096dfd
--- /dev/null
+++ b/docs/migration.md
@@ -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
+-
++
+```
+
+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.
diff --git a/docs/rules.md b/docs/rules.md
index a35c689..edc0e64 100644
--- a/docs/rules.md
+++ b/docs/rules.md
@@ -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 |
@@ -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 |
@@ -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
diff --git a/docs/rules/deny-import-from-ionic-module.md b/docs/rules/deny-import-from-ionic-module.md
deleted file mode 100644
index 2bfc004..0000000
--- a/docs/rules/deny-import-from-ionic-module.md
+++ /dev/null
@@ -1,31 +0,0 @@
-# @rdlabo/rules/deny-import-from-ionic-module
-
-> This plugin prevents accidental imports from @ionic/angular instead of @ionic/angular/standalone.
->
-> - ⭐️ 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.
-
-Mixing imports from `@ionic/angular` and `@ionic/angular/standalone` can cause runtime issues. While the build process may succeed, the application might fail when running in the browser. This rule helps prevent such issues by enforcing consistent import paths.
-
-## Rule Details
-
-❌ Incorrect: Importing from `@ionic/angular`
-
-```ts
-import { ModalController } from '@ionic/angular';
-```
-
-✅ Correct: Importing from `@ionic/angular/standalone`
-
-```ts
-import { ModalController } from '@ionic/angular/standalone';
-```
-
-## Options
-
-No Options.
-
-## Implementation
-
-- [Rule source](../../src/rules/deny-import-from-ionic-module.ts)
-- [Test source](../../tests/rules/deny-import-from-ionic-module.ts)
diff --git a/docs/rules/prefer-ionic-standalone.md b/docs/rules/prefer-ionic-standalone.md
new file mode 100644
index 0000000..c5d44b4
--- /dev/null
+++ b/docs/rules/prefer-ionic-standalone.md
@@ -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)
diff --git a/package-lock.json b/package-lock.json
index cc549a2..0536cce 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -12,14 +12,15 @@
"ts-api-utils": "2.1.0"
},
"devDependencies": {
- "@angular-eslint/template-parser": "^21.0.0",
- "@angular-eslint/test-utils": "^21.0.0",
- "@angular/compiler": "^21.0.0",
- "@ionic/core": "^8.7.14",
+ "@angular-eslint/template-parser": "^22.1.0",
+ "@angular-eslint/test-utils": "^22.1.0",
+ "@angular/compiler": "^22.1.2",
+ "@ionic/core": "^9.0.0",
"@types/eslint": "^9.0.0",
"@types/jest": "^29.5.0",
- "@typescript-eslint/rule-tester": "^8.33.0",
- "@typescript-eslint/utils": "8.50.0",
+ "@types/node": "^20.19.43",
+ "@typescript-eslint/rule-tester": "^8.67.0",
+ "@typescript-eslint/utils": "^8.67.0",
"eslint": "^9.0.0",
"husky": "^8.0.3",
"jest": "^29.7.0",
@@ -29,17 +30,18 @@
"prettier": "^3.8.4",
"rimraf": "^5.0.0",
"rxjs": "^7.8.2",
- "ts-jest": "^29.2.4",
+ "ts-jest": "^29.4.12",
"ts-node": "^10.9.0",
- "typescript": "^5.8.0",
- "typescript-eslint": "^8.33.0"
+ "typescript": "~6.0.3",
+ "typescript-eslint": "^8.67.0"
},
"engines": {
"node": ">=20.0.0"
},
"peerDependencies": {
- "@angular-eslint/template-parser": ">=21.0.0 <22.0.0",
- "@ionic/core": ">=8.0.0 <9.0.0",
+ "@angular-eslint/template-parser": ">=21.0.0 <23.0.0",
+ "@ionic/angular": ">=9.0.0 <10.0.0",
+ "@ionic/core": ">=9.0.0 <10.0.0",
"@typescript-eslint/utils": ">=8.33.0 <9.0.0",
"eslint": ">=9.0.0"
},
@@ -47,45 +49,48 @@
"@angular-eslint/template-parser": {
"optional": true
},
+ "@ionic/angular": {
+ "optional": true
+ },
"@ionic/core": {
"optional": true
}
}
},
"node_modules/@angular-eslint/bundled-angular-compiler": {
- "version": "21.1.0",
- "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-21.1.0.tgz",
- "integrity": "sha512-t52J6FszgEHaJ+IjuzU9qaWfVxsjlVNkAP+B5z2t4NDgbbDDsmI+QJh0OtP1qdlqzjh2pbocEml30KhYmNZm/Q==",
+ "version": "22.1.0",
+ "resolved": "https://registry.npmjs.org/@angular-eslint/bundled-angular-compiler/-/bundled-angular-compiler-22.1.0.tgz",
+ "integrity": "sha512-iOtOQ2jtrtko1rIQo6i+g3ezxGL0lyYv80j4GccFTK1JGh4K+AqYkmaBvfUfNtqoE/7VcKsOoyxaFt6iIpKhaQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@angular-eslint/template-parser": {
- "version": "21.1.0",
- "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-21.1.0.tgz",
- "integrity": "sha512-PYVgNbjNtuD5/QOuS6cHR8A7bRqsVqxtUUXGqdv76FYMAajQcAvyfR0QxOkqf3NmYxgNgO3hlUHWq0ILjVbcow==",
+ "version": "22.1.0",
+ "resolved": "https://registry.npmjs.org/@angular-eslint/template-parser/-/template-parser-22.1.0.tgz",
+ "integrity": "sha512-gcufZLI/Rl2fOWtBk2MgMRkH1t+OrbJGxIsfT0w3pVjKm0zwi7njM/dtPbVjCHzsGhx7szQMvY0i0UwTBVYkSw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@angular-eslint/bundled-angular-compiler": "21.1.0",
- "eslint-scope": "^9.0.0"
+ "@angular-eslint/bundled-angular-compiler": "22.1.0",
+ "eslint-scope": "9.1.2"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
+ "eslint": "^9.0.0 || ^10.0.0",
"typescript": "*"
}
},
"node_modules/@angular-eslint/test-utils": {
- "version": "21.1.0",
- "resolved": "https://registry.npmjs.org/@angular-eslint/test-utils/-/test-utils-21.1.0.tgz",
- "integrity": "sha512-Tn0+dZgeJ9HPku5noEl7g+vcTuMX6qOriJzcfJWZTeIc+n5Ld4SL/sZcorw3l0sBChSznjnyBM/aNk6fL6UQxA==",
+ "version": "22.1.0",
+ "resolved": "https://registry.npmjs.org/@angular-eslint/test-utils/-/test-utils-22.1.0.tgz",
+ "integrity": "sha512-BBpxOIF3c5LEHwq1veMc0o3VUH++ll1h2EDAL2CVaFBHEQH50HEKh7PDY5Pesx5hdVExFEs466X9iriBbndD0A==",
"dev": true,
"license": "MIT",
"peerDependencies": {
- "@angular-eslint/template-parser": "21.1.0",
- "@typescript-eslint/parser": "^7.11.0 || ^8.0.0",
- "@typescript-eslint/rule-tester": "^7.11.0 || ^8.0.0",
- "@typescript-eslint/utils": "^7.11.0 || ^8.0.0",
- "eslint": "^8.57.0 || ^9.0.0",
+ "@angular-eslint/template-parser": "22.1.0",
+ "@typescript-eslint/parser": "^8.0.0",
+ "@typescript-eslint/rule-tester": "^8.0.0",
+ "@typescript-eslint/utils": "^8.0.0",
+ "eslint": "^9.0.0 || ^10.0.0",
"typescript": "*"
},
"peerDependenciesMeta": {
@@ -98,16 +103,16 @@
}
},
"node_modules/@angular/compiler": {
- "version": "21.0.6",
- "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-21.0.6.tgz",
- "integrity": "sha512-rBMzG7WnQMouFfDST+daNSAOVYdtw560645PhlxyVeIeHMlCm0j1jjBgVPGTBNpVgKRdT/sqbi6W6JYkY9mERA==",
- "dev": true,
+ "version": "22.1.3",
+ "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-22.1.3.tgz",
+ "integrity": "sha512-L8Mw2r7bGG/obqgQC+RU3mdFJ3NtLgO5gWhEC1ylcHpLCMPIAXYsMKJIL8dnS78S1wXo/omXwmJ4FiIlCwWahg==",
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
},
"engines": {
- "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ "node": "^22.22.3 || ^24.15.0 || >=26.0.0"
}
},
"node_modules/@babel/code-frame": {
@@ -1292,14 +1297,14 @@
}
},
"node_modules/@ionic/core": {
- "version": "8.8.18",
- "resolved": "https://registry.npmjs.org/@ionic/core/-/core-8.8.18.tgz",
- "integrity": "sha512-QRFqi6gMSTk89FMjtoDx3p6e2dD7mYxqYzS0+Hl+yjxnoWoNFluWKM9rnGEEkdvpY7bF1pt6CI5IwMt+vjYQWg==",
- "dev": true,
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/@ionic/core/-/core-9.0.0.tgz",
+ "integrity": "sha512-ouHmLIdyP0vTV0PK/kj0QNtQTcDjyoWi2vKRaCkwj0xY3oU9qq3lB0N6w3Cm93ol0inBuXgn3/IN71BTp88c+g==",
+ "devOptional": true,
"license": "MIT",
"dependencies": {
- "@stencil/core": "4.43.5",
- "ionicons": "^8.0.13",
+ "@stencil/core": "^4.43.5",
+ "ionicons": "^8.1.0",
"tslib": "^2.1.0"
},
"engines": {
@@ -1913,7 +1918,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1927,7 +1931,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -1941,10 +1944,6 @@
"cpu": [
"arm64"
],
- "dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1958,10 +1957,6 @@
"cpu": [
"arm64"
],
- "dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1975,10 +1970,6 @@
"cpu": [
"x64"
],
- "dev": true,
- "libc": [
- "glibc"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -1992,10 +1983,6 @@
"cpu": [
"x64"
],
- "dev": true,
- "libc": [
- "musl"
- ],
"license": "MIT",
"optional": true,
"os": [
@@ -2009,7 +1996,6 @@
"cpu": [
"arm64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2023,7 +2009,6 @@
"cpu": [
"x64"
],
- "dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -2095,7 +2080,7 @@
"version": "4.43.5",
"resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.43.5.tgz",
"integrity": "sha512-cgWD+GeuvJpTe1WQn40p02+BJ2j0j1YJ17GdkF2qKIQ23s2e3Zivq5yISXS3dcuV6oUJFN93jprdk+nk/sq99Q==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"bin": {
"stencil": "bin/stencil"
@@ -2199,6 +2184,13 @@
"@types/json-schema": "*"
}
},
+ "node_modules/@types/esrecurse": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz",
+ "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -2262,13 +2254,13 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "25.0.3",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.3.tgz",
- "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==",
+ "version": "20.19.43",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.43.tgz",
+ "integrity": "sha512-6oYBAi5ikg4Pl+kGsoYtawUMBT2zZMCvPNF7pVLnHZfd1zf38DRiWn/gT01RYCdUqkv7Fhr+C9ot4/tb+2sVvA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "undici-types": "~7.16.0"
+ "undici-types": "~6.21.0"
}
},
"node_modules/@types/normalize-package-data": {
@@ -2303,20 +2295,20 @@
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": {
- "version": "8.50.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.50.0.tgz",
- "integrity": "sha512-O7QnmOXYKVtPrfYzMolrCTfkezCJS9+ljLdKW/+DCvRsc3UAz+sbH6Xcsv7p30+0OwUbeWfUDAQE0vpabZ3QLg==",
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.67.0.tgz",
+ "integrity": "sha512-Un7Heoyj65NREbKAyIrFxeM143NZpExWmy1Nep4DLeQOeLlTeumPjoNKnBrU5D5moWXbPJgRa5Uwcdu0faVNGQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@eslint-community/regexpp": "^4.10.0",
- "@typescript-eslint/scope-manager": "8.50.0",
- "@typescript-eslint/type-utils": "8.50.0",
- "@typescript-eslint/utils": "8.50.0",
- "@typescript-eslint/visitor-keys": "8.50.0",
- "ignore": "^7.0.0",
+ "@eslint-community/regexpp": "^4.12.2",
+ "@typescript-eslint/scope-manager": "8.67.0",
+ "@typescript-eslint/type-utils": "8.67.0",
+ "@typescript-eslint/utils": "8.67.0",
+ "@typescript-eslint/visitor-keys": "8.67.0",
+ "ignore": "^7.0.5",
"natural-compare": "^1.4.0",
- "ts-api-utils": "^2.1.0"
+ "ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2326,33 +2318,46 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "@typescript-eslint/parser": "^8.50.0",
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
+ "@typescript-eslint/parser": "^8.67.0",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
- "version": "7.0.5",
- "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
- "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz",
+ "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ts-api-utils": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+ "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
"node_modules/@typescript-eslint/parser": {
- "version": "8.50.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.50.0.tgz",
- "integrity": "sha512-6/cmF2piao+f6wSxUsJLZjck7OQsYyRtcOZS02k7XINSNlz93v6emM8WutDQSXnroG2xwYlEVHJI+cPA7CPM3Q==",
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.67.0.tgz",
+ "integrity": "sha512-fUBfTuuEulWqX6V8+O3PtScV01tzYYRUDTAirHFKoRAt7nOzoGiPt0M/bB47wWNy0coOOcgEwAMUtBpykMxl6w==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/scope-manager": "8.50.0",
- "@typescript-eslint/types": "8.50.0",
- "@typescript-eslint/typescript-estree": "8.50.0",
- "@typescript-eslint/visitor-keys": "8.50.0",
- "debug": "^4.3.4"
+ "@typescript-eslint/scope-manager": "8.67.0",
+ "@typescript-eslint/types": "8.67.0",
+ "@typescript-eslint/typescript-estree": "8.67.0",
+ "@typescript-eslint/visitor-keys": "8.67.0",
+ "debug": "^4.4.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2362,20 +2367,20 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/project-service": {
- "version": "8.50.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.50.0.tgz",
- "integrity": "sha512-Cg/nQcL1BcoTijEWyx4mkVC56r8dj44bFDvBdygifuS20f3OZCHmFbjF34DPSi07kwlFvqfv/xOLnJ5DquxSGQ==",
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.67.0.tgz",
+ "integrity": "sha512-cvE8c7ulYeXN9fYuszhCeCsbzyVEXuhrRCybnBre7TUmqb5nRmBfQAwCj0O3WJFDeyAZt4VYv51vMCC9LHSdYw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/tsconfig-utils": "^8.50.0",
- "@typescript-eslint/types": "^8.50.0",
- "debug": "^4.3.4"
+ "@typescript-eslint/tsconfig-utils": "^8.67.0",
+ "@typescript-eslint/types": "^8.67.0",
+ "debug": "^4.4.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2385,23 +2390,23 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "typescript": ">=4.8.4 <6.0.0"
+ "typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/rule-tester": {
- "version": "8.50.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/rule-tester/-/rule-tester-8.50.0.tgz",
- "integrity": "sha512-bX8PGNjsVKjCLuS/5u0OkJx2LGOx7itTmbUAuraXr4VNfw6+nhWQJrQZu9AyX6YZgcv0pn4E8QI/Hh8aMR5WnQ==",
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/rule-tester/-/rule-tester-8.67.0.tgz",
+ "integrity": "sha512-aOPA7+XgoRe4idCQZH9V1+ObHW6wslSp/GWwXoRwsTFUW1AZnHVvcnCFvQwTUdDtOhzwT3F/G2nOls7UuiOBiA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/parser": "8.50.0",
- "@typescript-eslint/typescript-estree": "8.50.0",
- "@typescript-eslint/utils": "8.50.0",
+ "@typescript-eslint/parser": "8.67.0",
+ "@typescript-eslint/typescript-estree": "8.67.0",
+ "@typescript-eslint/utils": "8.67.0",
"ajv": "^6.12.6",
"json-stable-stringify-without-jsonify": "^1.0.1",
"lodash.merge": "4.6.2",
- "semver": "^7.6.0"
+ "semver": "^7.7.3"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2411,18 +2416,19 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0"
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/scope-manager": {
- "version": "8.50.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.50.0.tgz",
- "integrity": "sha512-xCwfuCZjhIqy7+HKxBLrDVT5q/iq7XBVBXLn57RTIIpelLtEIZHXAF/Upa3+gaCpeV1NNS5Z9A+ID6jn50VD4A==",
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.67.0.tgz",
+ "integrity": "sha512-EgvsleTwS4E+WzzSvem8fAUubLwatMNF1B5hHSLQxcvs7q2dtRhGyujHwLJSYlG41niJ7GP24Aha2+0mb1b2kg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.50.0",
- "@typescript-eslint/visitor-keys": "8.50.0"
+ "@typescript-eslint/types": "8.67.0",
+ "@typescript-eslint/visitor-keys": "8.67.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2433,9 +2439,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
- "version": "8.50.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.50.0.tgz",
- "integrity": "sha512-vxd3G/ybKTSlm31MOA96gqvrRGv9RJ7LGtZCn2Vrc5htA0zCDvcMqUkifcjrWNNKXHUU3WCkYOzzVSFBd0wa2w==",
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.67.0.tgz",
+ "integrity": "sha512-vV+LUSv5njUWsknE71fqKTlXUva+R76SaeORd6Zojcunk/6DvKFXONU3BrAs2H49mbygUXt6gbYunzwqNwlhdg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2446,21 +2452,21 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "typescript": ">=4.8.4 <6.0.0"
+ "typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/type-utils": {
- "version": "8.50.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.50.0.tgz",
- "integrity": "sha512-7OciHT2lKCewR0mFoBrvZJ4AXTMe/sYOe87289WAViOocEmDjjv8MvIOT2XESuKj9jp8u3SZYUSh89QA4S1kQw==",
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.67.0.tgz",
+ "integrity": "sha512-aVWDXbRmdXO9siTfX4ditQI1T9+zVcNazT48EJCD0v40/9RIFoUgZ05CmGEq9H2gixRpjUn/iplwvlcvutJW/Q==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.50.0",
- "@typescript-eslint/typescript-estree": "8.50.0",
- "@typescript-eslint/utils": "8.50.0",
- "debug": "^4.3.4",
- "ts-api-utils": "^2.1.0"
+ "@typescript-eslint/types": "8.67.0",
+ "@typescript-eslint/typescript-estree": "8.67.0",
+ "@typescript-eslint/utils": "8.67.0",
+ "debug": "^4.4.3",
+ "ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2470,14 +2476,27 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils/node_modules/ts-api-utils": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+ "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
}
},
"node_modules/@typescript-eslint/types": {
- "version": "8.50.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.50.0.tgz",
- "integrity": "sha512-iX1mgmGrXdANhhITbpp2QQM2fGehBse9LbTf0sidWK6yg/NE+uhV5dfU1g6EYPlcReYmkE9QLPq/2irKAmtS9w==",
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.67.0.tgz",
+ "integrity": "sha512-sBtgslww8nsMYUjhdPBiSyUqSzT8uR6g93A2QXnQC8+cGdjz0CyaOdqHDRJb1AtORbZCNUJBBeFA/tNR2uQmww==",
"dev": true,
"license": "MIT",
"engines": {
@@ -2489,21 +2508,21 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
- "version": "8.50.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.50.0.tgz",
- "integrity": "sha512-W7SVAGBR/IX7zm1t70Yujpbk+zdPq/u4soeFSknWFdXIFuWsBGBOUu/Tn/I6KHSKvSh91OiMuaSnYp3mtPt5IQ==",
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.67.0.tgz",
+ "integrity": "sha512-EKQBCE9yNlRJYm7jdTW5AhDacDUmSwQb0FAJAmK2EKYrNXIsa2vxcSZx6PvJ/dEdI6lS+Y9W+EXckLj0iPFGcw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/project-service": "8.50.0",
- "@typescript-eslint/tsconfig-utils": "8.50.0",
- "@typescript-eslint/types": "8.50.0",
- "@typescript-eslint/visitor-keys": "8.50.0",
- "debug": "^4.3.4",
- "minimatch": "^9.0.4",
- "semver": "^7.6.0",
+ "@typescript-eslint/project-service": "8.67.0",
+ "@typescript-eslint/tsconfig-utils": "8.67.0",
+ "@typescript-eslint/types": "8.67.0",
+ "@typescript-eslint/visitor-keys": "8.67.0",
+ "debug": "^4.4.3",
+ "minimatch": "^10.2.2",
+ "semver": "^7.7.3",
"tinyglobby": "^0.2.15",
- "ts-api-utils": "^2.1.0"
+ "ts-api-utils": "^2.5.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2513,20 +2532,72 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "typescript": ">=4.8.4 <6.0.0"
+ "typescript": ">=4.8.4 <6.1.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "5.0.9",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
+ "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "10.2.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz",
+ "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.8"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/ts-api-utils": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+ "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
}
},
"node_modules/@typescript-eslint/utils": {
- "version": "8.50.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.50.0.tgz",
- "integrity": "sha512-87KgUXET09CRjGCi2Ejxy3PULXna63/bMYv72tCAlDJC3Yqwln0HiFJ3VJMst2+mEtNtZu5oFvX4qJGjKsnAgg==",
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.67.0.tgz",
+ "integrity": "sha512-U9D1FdwEWBwok3hxxSdhclMb0twvt9QnjIQ0VfQ1AiX2epnpSgv2ubVDsayOFyY8K6FX+AQ7E0FKWVG3iKsj1A==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@eslint-community/eslint-utils": "^4.7.0",
- "@typescript-eslint/scope-manager": "8.50.0",
- "@typescript-eslint/types": "8.50.0",
- "@typescript-eslint/typescript-estree": "8.50.0"
+ "@eslint-community/eslint-utils": "^4.9.1",
+ "@typescript-eslint/scope-manager": "8.67.0",
+ "@typescript-eslint/types": "8.67.0",
+ "@typescript-eslint/typescript-estree": "8.67.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2536,19 +2607,19 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
- "version": "8.50.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.50.0.tgz",
- "integrity": "sha512-Xzmnb58+Db78gT/CCj/PVCvK+zxbnsw6F+O1oheYszJbBSdEjVhQi3C/Xttzxgi/GLmpvOggRs1RFpiJ8+c34Q==",
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.67.0.tgz",
+ "integrity": "sha512-fkv8dHRDqfGtTHuJeebdrQ7cX6Ad4WAS00rgHh9UGvMycF1mjBfsxry1XsLIFhWZ6Judlh6UdzK+TYlbpCXgnA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/types": "8.50.0",
- "eslint-visitor-keys": "^4.2.1"
+ "@typescript-eslint/types": "8.67.0",
+ "eslint-visitor-keys": "^5.0.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -2559,13 +2630,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
- "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
"dev": true,
"license": "Apache-2.0",
"engines": {
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ "node": "^20.19.0 || ^22.13.0 || >=24"
},
"funding": {
"url": "https://opencollective.com/eslint"
@@ -4296,12 +4367,14 @@
}
},
"node_modules/eslint-scope": {
- "version": "9.0.0",
- "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.0.0.tgz",
- "integrity": "sha512-+Yh0LeQKq+mW/tQArNj67tljR3L1HajDTQPuZOEwC00oBdoIDQrr89yBgjAlzAwRrY/5zDkM3v99iGHwz9y0dw==",
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz",
+ "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
+ "@types/esrecurse": "^4.3.1",
+ "@types/estree": "^1.0.8",
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
},
@@ -5169,9 +5242,9 @@
"license": "ISC"
},
"node_modules/handlebars": {
- "version": "4.7.8",
- "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz",
- "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==",
+ "version": "4.7.9",
+ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz",
+ "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -5847,7 +5920,7 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/ionicons/-/ionicons-8.1.0.tgz",
"integrity": "sha512-XSM2gYWTXxSYTwjmKWAoy4yR7AbAFDjpc5ZIivAiGXskM0fe5CdMPfs6InQRUcrYtVb2WZ/0HQ1/jeh3JW78SA==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"@stencil/core": "^4.43.5"
@@ -10417,7 +10490,7 @@
"version": "7.8.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
- "dev": true,
+ "devOptional": true,
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.1.0"
@@ -10499,9 +10572,9 @@
}
},
"node_modules/semver": {
- "version": "7.7.3",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
- "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
+ "version": "7.8.5",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz",
+ "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==",
"dev": true,
"license": "ISC",
"bin": {
@@ -11229,14 +11302,14 @@
"license": "MIT"
},
"node_modules/tinyglobby": {
- "version": "0.2.15",
- "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
- "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "version": "0.2.17",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz",
+ "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
- "picomatch": "^4.0.3"
+ "picomatch": "^4.0.4"
},
"engines": {
"node": ">=12.0.0"
@@ -11264,9 +11337,9 @@
}
},
"node_modules/tinyglobby/node_modules/picomatch": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
- "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz",
+ "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==",
"dev": true,
"license": "MIT",
"engines": {
@@ -11322,19 +11395,19 @@
}
},
"node_modules/ts-jest": {
- "version": "29.4.6",
- "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz",
- "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==",
+ "version": "29.4.12",
+ "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.12.tgz",
+ "integrity": "sha512-Ov6ClY53Fflh6BGAnY2DlTq1hYDrTycz2PVTXBWFW2CU+9zrEqAp9fWdGXl42EXO5RLSFAcAZ2JFKbP+zBTFfw==",
"dev": true,
"license": "MIT",
"dependencies": {
"bs-logger": "^0.2.6",
"fast-json-stable-stringify": "^2.1.0",
- "handlebars": "^4.7.8",
+ "handlebars": "^4.7.9",
"json5": "^2.2.3",
"lodash.memoize": "^4.1.2",
"make-error": "^1.3.6",
- "semver": "^7.7.3",
+ "semver": "^7.8.5",
"type-fest": "^4.41.0",
"yargs-parser": "^21.1.1"
},
@@ -11351,7 +11424,7 @@
"babel-jest": "^29.0.0 || ^30.0.0",
"jest": "^29.0.0 || ^30.0.0",
"jest-util": "^29.0.0 || ^30.0.0",
- "typescript": ">=4.3 <6"
+ "typescript": ">=4.3 <7"
},
"peerDependenciesMeta": {
"@babel/core": {
@@ -11435,7 +11508,7 @@
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
- "dev": true,
+ "devOptional": true,
"license": "0BSD"
},
"node_modules/type-check": {
@@ -11553,9 +11626,9 @@
}
},
"node_modules/typescript": {
- "version": "5.9.3",
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
- "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz",
+ "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==",
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
@@ -11566,16 +11639,16 @@
}
},
"node_modules/typescript-eslint": {
- "version": "8.50.0",
- "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.50.0.tgz",
- "integrity": "sha512-Q1/6yNUmCpH94fbgMUMg2/BSAr/6U7GBk61kZTv1/asghQOWOjTlp9K8mixS5NcJmm2creY+UFfGeW/+OcA64A==",
+ "version": "8.67.0",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.67.0.tgz",
+ "integrity": "sha512-S2udFs8tCKEKffuJ4TB1idGUZiXdCPGi3IPBGWXarbLQ5UPXORV8QEVzJ4gCRduURMb5EkpNCdjbk0eDIuI8Yg==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@typescript-eslint/eslint-plugin": "8.50.0",
- "@typescript-eslint/parser": "8.50.0",
- "@typescript-eslint/typescript-estree": "8.50.0",
- "@typescript-eslint/utils": "8.50.0"
+ "@typescript-eslint/eslint-plugin": "8.67.0",
+ "@typescript-eslint/parser": "8.67.0",
+ "@typescript-eslint/typescript-estree": "8.67.0",
+ "@typescript-eslint/utils": "8.67.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -11585,8 +11658,8 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
- "eslint": "^8.57.0 || ^9.0.0",
- "typescript": ">=4.8.4 <6.0.0"
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.1.0"
}
},
"node_modules/uglify-js": {
@@ -11623,9 +11696,9 @@
}
},
"node_modules/undici-types": {
- "version": "7.16.0",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
- "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
"dev": true,
"license": "MIT"
},
diff --git a/package.json b/package.json
index bea439b..fc9ec56 100644
--- a/package.json
+++ b/package.json
@@ -37,13 +37,14 @@
"prepack": "run-s clean build",
"prerelease": "npm run build",
"release": "np --no-tests --no-publish",
- "add-rule": "ts-node scripts/add-rule",
- "update": "ts-node scripts/update",
+ "add-rule": "ts-node --project scripts/tsconfig.json scripts/add-rule",
+ "update": "ts-node --project scripts/tsconfig.json scripts/update",
"inspect": "npx @eslint/config-inspector"
},
"peerDependencies": {
- "@angular-eslint/template-parser": ">=21.0.0 <22.0.0",
- "@ionic/core": ">=8.0.0 <9.0.0",
+ "@angular-eslint/template-parser": ">=21.0.0 <23.0.0",
+ "@ionic/angular": ">=9.0.0 <10.0.0",
+ "@ionic/core": ">=9.0.0 <10.0.0",
"@typescript-eslint/utils": ">=8.33.0 <9.0.0",
"eslint": ">=9.0.0"
},
@@ -51,19 +52,23 @@
"@angular-eslint/template-parser": {
"optional": true
},
+ "@ionic/angular": {
+ "optional": true
+ },
"@ionic/core": {
"optional": true
}
},
"devDependencies": {
- "@angular-eslint/template-parser": "^21.0.0",
- "@angular-eslint/test-utils": "^21.0.0",
- "@angular/compiler": "^21.0.0",
- "@ionic/core": "^8.7.14",
+ "@angular-eslint/template-parser": "^22.1.0",
+ "@angular-eslint/test-utils": "^22.1.0",
+ "@angular/compiler": "^22.1.2",
+ "@ionic/core": "^9.0.0",
"@types/eslint": "^9.0.0",
"@types/jest": "^29.5.0",
- "@typescript-eslint/rule-tester": "^8.33.0",
- "@typescript-eslint/utils": "8.50.0",
+ "@types/node": "^20.19.43",
+ "@typescript-eslint/rule-tester": "^8.67.0",
+ "@typescript-eslint/utils": "^8.67.0",
"eslint": "^9.0.0",
"husky": "^8.0.3",
"jest": "^29.7.0",
@@ -73,10 +78,10 @@
"prettier": "^3.8.4",
"rimraf": "^5.0.0",
"rxjs": "^7.8.2",
- "ts-jest": "^29.2.4",
+ "ts-jest": "^29.4.12",
"ts-node": "^10.9.0",
- "typescript": "^5.8.0",
- "typescript-eslint": "^8.33.0"
+ "typescript": "~6.0.3",
+ "typescript-eslint": "^8.67.0"
},
"lint-staged": {
"*.{js,ts}": [
diff --git a/scripts/check-compatibility-versions.mjs b/scripts/check-compatibility-versions.mjs
new file mode 100644
index 0000000..dcf4ef9
--- /dev/null
+++ b/scripts/check-compatibility-versions.mjs
@@ -0,0 +1,31 @@
+import { readFileSync } from 'node:fs';
+import { resolve } from 'node:path';
+
+const [angularMajor, typescriptRange, ionicAngularFixture] = process.argv.slice(2);
+if (!angularMajor || !typescriptRange || !ionicAngularFixture) {
+ throw new Error('Usage: node scripts/check-compatibility-versions.mjs ');
+}
+
+const expectedMajors = new Map([
+ ['@angular-eslint/template-parser', angularMajor],
+ ['@angular-eslint/test-utils', angularMajor],
+ ['@angular/compiler', angularMajor],
+ ['@angular/core', angularMajor],
+ ['@ionic/core', '9'],
+ ['typescript', typescriptRange.match(/\d+/)?.[0]],
+]);
+
+for (const [packageName, expectedMajor] of expectedMajors) {
+ const packageJson = JSON.parse(readFileSync(new URL(`../node_modules/${packageName}/package.json`, import.meta.url), 'utf8'));
+ const actualMajor = String(packageJson.version).split('.')[0];
+ if (actualMajor !== expectedMajor) {
+ throw new Error(`${packageName}: expected major ${expectedMajor}, received ${packageJson.version}`);
+ }
+ console.log(`${packageName}@${packageJson.version}`);
+}
+
+const ionicAngularPackage = JSON.parse(readFileSync(resolve(ionicAngularFixture, 'node_modules/@ionic/angular/package.json'), 'utf8'));
+if (String(ionicAngularPackage.version).split('.')[0] !== '9') {
+ throw new Error(`@ionic/angular: expected major 9, received ${ionicAngularPackage.version}`);
+}
+console.log(`@ionic/angular@${ionicAngularPackage.version}`);
diff --git a/scripts/lib/recommended-rule-names.ts b/scripts/lib/recommended-rule-names.ts
index acba1c3..ac82dd1 100644
--- a/scripts/lib/recommended-rule-names.ts
+++ b/scripts/lib/recommended-rule-names.ts
@@ -2,7 +2,7 @@
export const RECOMMENDED_RULE_NAMES = new Set([
'signal-use-as-signal',
'signal-use-as-signal-template',
- 'deny-import-from-ionic-module',
+ 'prefer-ionic-standalone',
'implements-ionic-lifecycle',
'deny-soft-private-modifier',
'deny-overlay-create',
diff --git a/scripts/lib/update-lib-configs-recommended.ts b/scripts/lib/update-lib-configs-recommended.ts
index 390a1bc..2283349 100644
--- a/scripts/lib/update-lib-configs-recommended.ts
+++ b/scripts/lib/update-lib-configs-recommended.ts
@@ -25,7 +25,7 @@ const recommended: Linter.Config[] = [
rules: {
'@rdlabo/rules/signal-use-as-signal': 'error',
'@rdlabo/rules/signal-use-as-signal-template': 'error',
- '@rdlabo/rules/deny-import-from-ionic-module': 'error',
+ '@rdlabo/rules/prefer-ionic-standalone': 'error',
'@rdlabo/rules/implements-ionic-lifecycle': 'error',
'@rdlabo/rules/deny-soft-private-modifier': 'error',
'@rdlabo/rules/deny-overlay-create': 'error',
diff --git a/scripts/tsconfig.json b/scripts/tsconfig.json
index 0c84f3b..f609de1 100644
--- a/scripts/tsconfig.json
+++ b/scripts/tsconfig.json
@@ -11,7 +11,8 @@
"moduleResolution": "node",
"resolveJsonModule": true,
"esModuleInterop": true,
- "skipLibCheck": true
+ "skipLibCheck": true,
+ "types": ["node"]
},
- "include": ["lib"]
+ "include": ["**/*.ts"]
}
diff --git a/src/configs/recommended.ts b/src/configs/recommended.ts
index 195e0e0..05407a6 100644
--- a/src/configs/recommended.ts
+++ b/src/configs/recommended.ts
@@ -14,7 +14,7 @@ const recommended: Linter.Config[] = [
rules: {
'@rdlabo/rules/signal-use-as-signal': 'error',
'@rdlabo/rules/signal-use-as-signal-template': 'error',
- '@rdlabo/rules/deny-import-from-ionic-module': 'error',
+ '@rdlabo/rules/prefer-ionic-standalone': 'error',
'@rdlabo/rules/implements-ionic-lifecycle': 'error',
'@rdlabo/rules/deny-soft-private-modifier': 'error',
'@rdlabo/rules/deny-overlay-create': 'error',
diff --git a/src/index.ts b/src/index.ts
index 64ab409..1b260c5 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -4,7 +4,6 @@ import recommended from './configs/recommended';
import componentpropertyusereadonly from './rules/component-property-use-readonly';
import denyconstructordi from './rules/deny-constructor-di';
import denyelement from './rules/deny-element';
-import denyimportfromionicmodule from './rules/deny-import-from-ionic-module';
import denyoverlaycreate from './rules/deny-overlay-create';
import denysoftprivatemodifier from './rules/deny-soft-private-modifier';
import implementsioniclifecycle from './rules/implements-ionic-lifecycle';
@@ -14,6 +13,7 @@ import nocomponentwritablesignal from './rules/no-component-writable-signal';
import noreactiveforms from './rules/no-reactive-forms';
import notemplatedrivenforms from './rules/no-template-driven-forms';
import preferdisablehandler from './rules/prefer-disable-handler';
+import preferionicstandalone from './rules/prefer-ionic-standalone';
import prefermodallauncher from './rules/prefer-modal-launcher';
import requireviewmodel from './rules/require-viewmodel';
import restricttryblock from './rules/restrict-try-block';
@@ -28,7 +28,6 @@ export = {
'component-property-use-readonly': componentpropertyusereadonly,
'deny-constructor-di': denyconstructordi,
'deny-element': denyelement,
- 'deny-import-from-ionic-module': denyimportfromionicmodule,
'deny-overlay-create': denyoverlaycreate,
'deny-soft-private-modifier': denysoftprivatemodifier,
'implements-ionic-lifecycle': implementsioniclifecycle,
@@ -38,6 +37,7 @@ export = {
'no-reactive-forms': noreactiveforms,
'no-template-driven-forms': notemplatedrivenforms,
'prefer-disable-handler': preferdisablehandler,
+ 'prefer-ionic-standalone': preferionicstandalone,
'prefer-modal-launcher': prefermodallauncher,
'require-viewmodel': requireviewmodel,
'restrict-try-block': restricttryblock,
diff --git a/src/rules/deny-element.ts b/src/rules/deny-element.ts
index d0012be..badaec8 100644
--- a/src/rules/deny-element.ts
+++ b/src/rules/deny-element.ts
@@ -1,5 +1,5 @@
import { TSESLint } from '@typescript-eslint/utils';
-import type { TSESTree } from '@typescript-eslint/utils/dist/ts-estree';
+import type { TSESTree } from '@typescript-eslint/utils';
interface TemplateNode {
name: string;
diff --git a/src/rules/deny-import-from-ionic-module.ts b/src/rules/deny-import-from-ionic-module.ts
deleted file mode 100644
index 0532007..0000000
--- a/src/rules/deny-import-from-ionic-module.ts
+++ /dev/null
@@ -1,32 +0,0 @@
-import { TSESLint } from '@typescript-eslint/utils';
-
-const rule: TSESLint.RuleModule<'denyImportFromIonicModule', []> = {
- defaultOptions: [],
- meta: {
- docs: {
- description: 'This plugin prevents accidental imports from @ionic/angular instead of @ionic/angular/standalone.',
- url: '',
- },
- fixable: 'code',
- messages: {
- denyImportFromIonicModule: 'You must import from @ionic/angular/standalone instead of @ionic/angular.',
- },
- schema: [],
- type: 'problem',
- },
- create: (context) => ({
- ImportDeclaration(node) {
- if (node.source.value === '@ionic/angular') {
- context.report({
- node: node.source,
- messageId: 'denyImportFromIonicModule',
- fix: (fixer) => {
- return fixer.replaceText(node.source, "'@ionic/angular/standalone'");
- },
- });
- }
- },
- }),
-};
-
-export = rule;
diff --git a/src/rules/deny-soft-private-modifier.ts b/src/rules/deny-soft-private-modifier.ts
index 8351720..ecf27ab 100644
--- a/src/rules/deny-soft-private-modifier.ts
+++ b/src/rules/deny-soft-private-modifier.ts
@@ -1,5 +1,4 @@
import { TSESLint } from '@typescript-eslint/utils';
-import { RuleFix } from '@typescript-eslint/utils/dist/ts-eslint';
import { TSESTree } from '@typescript-eslint/types';
const rule: TSESLint.RuleModule<'denySoftPrivateModifier', []> = {
@@ -44,7 +43,7 @@ const rule: TSESLint.RuleModule<'denySoftPrivateModifier', []> = {
node,
messageId: 'denySoftPrivateModifier',
fix: (fixer) => {
- const fixes: RuleFix[] = [];
+ const fixes: TSESLint.RuleFix[] = [];
const firstToken = sourceCode.getFirstToken(node);
let privateToken = firstToken;
while (privateToken && privateToken.value !== 'private') {
@@ -70,7 +69,7 @@ const rule: TSESLint.RuleModule<'denySoftPrivateModifier', []> = {
node,
messageId: 'denySoftPrivateModifier',
fix: (fixer) => {
- const fixes: RuleFix[] = [];
+ const fixes: TSESLint.RuleFix[] = [];
const firstToken = sourceCode.getFirstToken(node);
let privateToken = firstToken;
while (privateToken && privateToken.value !== 'private') {
diff --git a/src/rules/implements-ionic-lifecycle.ts b/src/rules/implements-ionic-lifecycle.ts
index e062b09..3e9a535 100644
--- a/src/rules/implements-ionic-lifecycle.ts
+++ b/src/rules/implements-ionic-lifecycle.ts
@@ -1,6 +1,5 @@
-import { TSESLint } from '@typescript-eslint/utils';
-import { AST_NODE_TYPES } from '@typescript-eslint/types';
-import { ClassElement } from '@typescript-eslint/types/dist/generated/ast-spec';
+import { AST_NODE_TYPES } from '@typescript-eslint/utils';
+import type { TSESLint, TSESTree } from '@typescript-eslint/utils';
const rule: TSESLint.RuleModule<'implementsIonicLifecycle', []> = {
defaultOptions: [],
@@ -53,7 +52,7 @@ const rule: TSESLint.RuleModule<'implementsIonicLifecycle', []> = {
// クラス内で使われているライフサイクルメソッド
const usedLifecycleTypes: string[] = [];
- const usedLifecycleMethods: { type: string; node: ClassElement }[] = [];
+ const usedLifecycleMethods: { type: string; node: TSESTree.ClassElement }[] = [];
node.body.body.forEach((definition) => {
if (definition.type === 'MethodDefinition' && definition.key.type === 'Identifier' && lifecycleMethods.includes(definition.key.name)) {
const methodName = definition.key.name;
diff --git a/src/rules/ionic-attr-type-check.ts b/src/rules/ionic-attr-type-check.ts
index f4520a7..9b10b95 100644
--- a/src/rules/ionic-attr-type-check.ts
+++ b/src/rules/ionic-attr-type-check.ts
@@ -1,5 +1,5 @@
import { TSESLint } from '@typescript-eslint/utils';
-import type { TSESTree } from '@typescript-eslint/utils/dist/ts-estree';
+import type { TSESTree } from '@typescript-eslint/utils';
import type { TmplAstElement, TmplAstTextAttribute, TmplAstBoundAttribute } from '@angular/compiler';
import * as fs from 'fs';
import * as path from 'path';
@@ -357,10 +357,10 @@ const getStringLiteralValues = (elementName: string, attributeName: string): str
// 正しいboolean値を取得
const getCorrectBooleanValue = (value: string): string => {
const lowerValue = value.toLowerCase();
- if (lowerValue === 'true' || lowerValue === '1' || lowerValue === 'yes') {
+ if (lowerValue === 'true' || lowerValue === '1' || lowerValue === 'yes' || lowerValue === 'on') {
return 'true';
}
- if (lowerValue === 'false' || lowerValue === '0' || lowerValue === 'no') {
+ if (lowerValue === 'false' || lowerValue === '0' || lowerValue === 'no' || lowerValue === 'off') {
return 'false';
}
// 空文字列はtrueとして扱い、その他の値はfalseとして扱う
@@ -373,7 +373,16 @@ const getCorrectBooleanValue = (value: string): string => {
// boolean値の文字列表現かチェック
const isBooleanStringValue = (value: string): boolean => {
const lowerValue = value.toLowerCase();
- return lowerValue === 'true' || lowerValue === 'false' || lowerValue === '1' || lowerValue === '0' || lowerValue === 'yes' || lowerValue === 'no';
+ return (
+ lowerValue === 'true' ||
+ lowerValue === 'false' ||
+ lowerValue === '1' ||
+ lowerValue === '0' ||
+ lowerValue === 'yes' ||
+ lowerValue === 'no' ||
+ lowerValue === 'on' ||
+ lowerValue === 'off'
+ );
};
const rule: TSESLint.RuleModule<'ionic-attr-type-check', []> = {
diff --git a/src/rules/prefer-disable-handler.ts b/src/rules/prefer-disable-handler.ts
index 0a8991f..e85de9e 100644
--- a/src/rules/prefer-disable-handler.ts
+++ b/src/rules/prefer-disable-handler.ts
@@ -1,5 +1,5 @@
import { TSESLint } from '@typescript-eslint/utils';
-import type { TSESTree } from '@typescript-eslint/utils/dist/ts-estree';
+import type { TSESTree } from '@typescript-eslint/utils';
interface TemplateLoc {
start: { line: number; column: number };
diff --git a/src/rules/prefer-ionic-standalone.ts b/src/rules/prefer-ionic-standalone.ts
new file mode 100644
index 0000000..58a1c20
--- /dev/null
+++ b/src/rules/prefer-ionic-standalone.ts
@@ -0,0 +1,152 @@
+import { TSESLint } from '@typescript-eslint/utils';
+
+type MessageIds = 'preferRootEntrypoint' | 'noIonicModule';
+
+const IONIC_ANGULAR_ENTRYPOINT = '@ionic/angular';
+const DISALLOWED_ENTRYPOINTS = new Set(['@ionic/angular/standalone', '@ionic/angular/lazy']);
+
+const rule: TSESLint.RuleModule = {
+ defaultOptions: [],
+ meta: {
+ docs: {
+ description: 'Prefer the Ionic 9 standalone API and disallow IonicModule and obsolete or NgModule-based entry points.',
+ url: '',
+ },
+ fixable: 'code',
+ messages: {
+ preferRootEntrypoint: 'Import Ionic Angular APIs from @ionic/angular instead of {{ entrypoint }}.',
+ noIonicModule: 'IonicModule is not allowed. Import standalone Ionic components and configure Ionic with provideIonicAngular().',
+ },
+ schema: [],
+ type: 'suggestion',
+ },
+ create(context) {
+ const ionicNamespaces = new Map();
+ const sourceCode = context.sourceCode;
+ const replacement = (source: { range: [number, number] }): string => {
+ const sourceText = sourceCode.text.slice(source.range[0], source.range[1]);
+ const quote = sourceText.startsWith('"') ? '"' : "'";
+ return `${quote}${IONIC_ANGULAR_ENTRYPOINT}${quote}`;
+ };
+
+ return {
+ ImportDeclaration(node) {
+ const entrypoint = String(node.source.value);
+
+ if (DISALLOWED_ENTRYPOINTS.has(entrypoint)) {
+ const canFix = node.specifiers.length > 0 && node.specifiers.every((specifier) => specifier.type === 'ImportSpecifier');
+ context.report({
+ node: node.source,
+ messageId: 'preferRootEntrypoint',
+ data: { entrypoint },
+ fix: canFix ? (fixer) => fixer.replaceText(node.source, replacement(node.source)) : undefined,
+ });
+ return;
+ }
+
+ if (entrypoint !== IONIC_ANGULAR_ENTRYPOINT) {
+ return;
+ }
+
+ for (const specifier of node.specifiers) {
+ if (specifier.type === 'ImportNamespaceSpecifier') {
+ const variable = sourceCode.getDeclaredVariables(node).find((candidate) => candidate.name === specifier.local.name);
+ if (variable) {
+ ionicNamespaces.set(specifier.local.name, variable);
+ }
+ continue;
+ }
+ if (specifier.type === 'ImportSpecifier' && specifier.imported.type === 'Identifier' && specifier.imported.name === 'IonicModule') {
+ context.report({
+ node: specifier,
+ messageId: 'noIonicModule',
+ });
+ }
+ }
+ },
+ ExportNamedDeclaration(node) {
+ if (!node.source) {
+ return;
+ }
+
+ const entrypoint = String(node.source.value);
+ if (DISALLOWED_ENTRYPOINTS.has(entrypoint)) {
+ context.report({
+ node: node.source,
+ messageId: 'preferRootEntrypoint',
+ data: { entrypoint },
+ fix: node.specifiers.length > 0 ? (fixer) => fixer.replaceText(node.source!, replacement(node.source!)) : undefined,
+ });
+ return;
+ }
+
+ if (entrypoint !== IONIC_ANGULAR_ENTRYPOINT) {
+ return;
+ }
+
+ for (const specifier of node.specifiers) {
+ const exportedName = specifier.local.type === 'Identifier' ? specifier.local.name : specifier.local.value;
+ if (exportedName === 'IonicModule') {
+ context.report({
+ node: specifier,
+ messageId: 'noIonicModule',
+ });
+ }
+ }
+ },
+ ExportAllDeclaration(node) {
+ const entrypoint = String(node.source.value);
+ if (DISALLOWED_ENTRYPOINTS.has(entrypoint)) {
+ context.report({
+ node: node.source,
+ messageId: 'preferRootEntrypoint',
+ data: { entrypoint },
+ });
+ } else if (entrypoint === IONIC_ANGULAR_ENTRYPOINT) {
+ context.report({
+ node: node.source,
+ messageId: 'noIonicModule',
+ });
+ }
+ },
+ MemberExpression(node) {
+ if (node.object.type !== 'Identifier') {
+ return;
+ }
+
+ const ionicNamespace = ionicNamespaces.get(node.object.name);
+ if (!ionicNamespace) {
+ return;
+ }
+
+ let scope: TSESLint.Scope.Scope | null = sourceCode.getScope(node);
+ let resolvedVariable: TSESLint.Scope.Variable | undefined;
+ while (scope) {
+ resolvedVariable = scope.set.get(node.object.name);
+ if (resolvedVariable) {
+ break;
+ }
+ scope = scope.upper;
+ }
+ if (resolvedVariable !== ionicNamespace) {
+ return;
+ }
+
+ const propertyName =
+ node.property.type === 'Identifier' && !node.computed
+ ? node.property.name
+ : node.property.type === 'Literal' && typeof node.property.value === 'string'
+ ? node.property.value
+ : undefined;
+ if (propertyName === 'IonicModule') {
+ context.report({
+ node,
+ messageId: 'noIonicModule',
+ });
+ }
+ },
+ };
+ },
+};
+
+export = rule;
diff --git a/src/rules/signal-use-as-signal-template.ts b/src/rules/signal-use-as-signal-template.ts
index 4c318d4..e672efe 100644
--- a/src/rules/signal-use-as-signal-template.ts
+++ b/src/rules/signal-use-as-signal-template.ts
@@ -3,7 +3,7 @@ import * as fs from 'fs';
import * as path from 'path';
import { AST_NODE_TYPES } from '@typescript-eslint/types';
import { parseForESLint } from '@angular-eslint/template-parser';
-import type { TSESTree } from '@typescript-eslint/utils/dist/ts-estree';
+import type { TSESTree } from '@typescript-eslint/utils';
import type { TmplAstNode, TmplAstDeferredBlock } from '@angular/compiler';
import type { DecoratorProperties, TemplateInfo, TemplateExpression, StarLine } from './types';
import { shiftLocLine, isSignalCallExpression } from './utils';
diff --git a/src/rules/types.ts b/src/rules/types.ts
index 5f8a77f..44baddd 100644
--- a/src/rules/types.ts
+++ b/src/rules/types.ts
@@ -1,4 +1,4 @@
-import type { TSESTree } from '@typescript-eslint/utils/dist/ts-estree';
+import type { TSESTree } from '@typescript-eslint/utils';
import { AST } from 'eslint';
import SourceLocation = AST.SourceLocation;
import { TemplateLiteral } from '@angular/compiler';
diff --git a/tests/rules/deny-import-from-ionic-module.ts b/tests/rules/deny-import-from-ionic-module.ts
deleted file mode 100644
index 6c2064a..0000000
--- a/tests/rules/deny-import-from-ionic-module.ts
+++ /dev/null
@@ -1,74 +0,0 @@
-import { RuleTester } from '@angular-eslint/test-utils';
-import rule from '../../src/rules/deny-import-from-ionic-module';
-
-new RuleTester().run('deny-import-from-ionic-module', rule, {
- valid: [
- {
- code: `
- import { ModalController } from '@ionic/angular/standalone';
- `,
- },
- {
- code: `
- import { AlertController, ModalController } from '@ionic/angular/standalone';
- `,
- },
- {
- code: `
- import { ModalController } from '@ionic/angular/standalone';
- import { AlertController } from '@ionic/angular/standalone';
- `,
- },
- {
- code: `
- import { ModalController } from '@ionic/angular/standalone';
- import { Component } from '@angular/core';
- `,
- },
- ],
- invalid: [
- {
- code: `
- import { ModalController } from '@ionic/angular';
- `,
- output: `
- import { ModalController } from '@ionic/angular/standalone';
- `,
- errors: [{ messageId: 'denyImportFromIonicModule', line: 2 }],
- },
- {
- code: `
- import { AlertController, ModalController } from '@ionic/angular';
- `,
- output: `
- import { AlertController, ModalController } from '@ionic/angular/standalone';
- `,
- errors: [{ messageId: 'denyImportFromIonicModule', line: 2 }],
- },
- {
- code: `
- import { ModalController } from '@ionic/angular';
- import { AlertController } from '@ionic/angular';
- `,
- output: `
- import { ModalController } from '@ionic/angular/standalone';
- import { AlertController } from '@ionic/angular/standalone';
- `,
- errors: [
- { messageId: 'denyImportFromIonicModule', line: 2 },
- { messageId: 'denyImportFromIonicModule', line: 3 },
- ],
- },
- {
- code: `
- import { ModalController } from '@ionic/angular';
- import { Component } from '@angular/core';
- `,
- output: `
- import { ModalController } from '@ionic/angular/standalone';
- import { Component } from '@angular/core';
- `,
- errors: [{ messageId: 'denyImportFromIonicModule', line: 2 }],
- },
- ],
-});
diff --git a/tests/rules/deny-overlay-create.ts b/tests/rules/deny-overlay-create.ts
index 3c7afb2..71f8003 100644
--- a/tests/rules/deny-overlay-create.ts
+++ b/tests/rules/deny-overlay-create.ts
@@ -6,7 +6,7 @@ new RuleTester().run('deny-overlay-create', rule, {
{
code: `
import { inject } from '@angular/core';
- import { LoadingController, AlertController, ModalController, ToastController, ActionSheetController } from '@ionic/angular/standalone';
+ import { LoadingController, AlertController, ModalController, ToastController, ActionSheetController } from '@ionic/angular';
export class ExamplePage {
readonly #loadingCtrl = inject(LoadingController);
@@ -40,7 +40,7 @@ new RuleTester().run('deny-overlay-create', rule, {
{
code: `
import { inject } from '@angular/core';
- import { ToastController } from '@ionic/angular/standalone';
+ import { ToastController } from '@ionic/angular';
export class ExamplePage {
readonly toastCtrl = inject(ToastController);
@@ -106,7 +106,7 @@ new RuleTester().run('deny-overlay-create', rule, {
{
code: `
import { inject } from '@angular/core';
- import { ModalController } from '@ionic/angular/standalone';
+ import { ModalController } from '@ionic/angular';
export class ExamplePage {
readonly modalCtrl = inject(ModalController);
@@ -120,7 +120,7 @@ new RuleTester().run('deny-overlay-create', rule, {
{
code: `
import { inject } from '@angular/core';
- import { ModalController, PopoverController } from '@ionic/angular/standalone';
+ import { ModalController, PopoverController } from '@ionic/angular';
export class ExamplePage {
readonly modalCtrl = inject(ModalController);
@@ -142,7 +142,7 @@ new RuleTester().run('deny-overlay-create', rule, {
{
code: `
import { inject } from '@angular/core';
- import { ModalController } from '@ionic/angular/standalone';
+ import { ModalController } from '@ionic/angular';
export class ExamplePage {
readonly #modalCtrl = inject(ModalController);
@@ -157,7 +157,7 @@ new RuleTester().run('deny-overlay-create', rule, {
{
code: `
import { inject } from '@angular/core';
- import { ModalController } from '@ionic/angular/standalone';
+ import { ModalController } from '@ionic/angular';
export class ExamplePage {
readonly modalCtrl = inject(ModalController);
@@ -172,7 +172,7 @@ new RuleTester().run('deny-overlay-create', rule, {
{
code: `
import { inject } from '@angular/core';
- import { PopoverController } from '@ionic/angular/standalone';
+ import { PopoverController } from '@ionic/angular';
export class ExamplePage {
readonly popoverCtrl = inject(PopoverController);
@@ -187,7 +187,7 @@ new RuleTester().run('deny-overlay-create', rule, {
{
code: `
import { inject } from '@angular/core';
- import { ModalController } from '@ionic/angular/standalone';
+ import { ModalController } from '@ionic/angular';
export class ExamplePage {
async open() {
@@ -199,7 +199,7 @@ new RuleTester().run('deny-overlay-create', rule, {
},
{
code: `
- import { ModalController } from '@ionic/angular/standalone';
+ import { ModalController } from '@ionic/angular';
export async function open(modalCtrl: ModalController) {
await modalCtrl.create({ component: OtherPage });
@@ -224,7 +224,7 @@ new RuleTester().run('deny-overlay-create', rule, {
},
{
code: `
- import { ModalController } from '@ionic/angular/standalone';
+ import { ModalController } from '@ionic/angular';
const open = async (modalCtrl: ModalController) => {
await modalCtrl.create({ component: OtherPage });
@@ -235,7 +235,7 @@ new RuleTester().run('deny-overlay-create', rule, {
{
code: `
import { inject } from '@angular/core';
- import { ModalController } from '@ionic/angular/standalone';
+ import { ModalController } from '@ionic/angular';
const modalCtrl = inject(ModalController);
await modalCtrl.create({ component: OtherPage });
@@ -244,7 +244,7 @@ new RuleTester().run('deny-overlay-create', rule, {
},
{
code: `
- import { ModalController } from '@ionic/angular/standalone';
+ import { ModalController } from '@ionic/angular';
export class ExamplePage {
constructor(private modalCtrl: ModalController) {}
@@ -258,7 +258,7 @@ new RuleTester().run('deny-overlay-create', rule, {
},
{
code: `
- import { ModalController } from '@ionic/angular/standalone';
+ import { ModalController } from '@ionic/angular';
export class ExamplePage {
modalCtrl: ModalController;
@@ -273,7 +273,7 @@ new RuleTester().run('deny-overlay-create', rule, {
{
code: `
import { inject } from '@angular/core';
- import { ModalController, PopoverController } from '@ionic/angular/standalone';
+ import { ModalController, PopoverController } from '@ionic/angular';
export class ExamplePage {
readonly modalCtrl = inject(ModalController);
@@ -293,7 +293,7 @@ new RuleTester().run('deny-overlay-create', rule, {
{
code: `
import { inject } from '@angular/core';
- import { AlertController } from '@ionic/angular/standalone';
+ import { AlertController } from '@ionic/angular';
export class ExamplePage {
readonly alertCtrl = inject(AlertController);
diff --git a/tests/rules/ionic-attr-type-check-boolean.ts b/tests/rules/ionic-attr-type-check-boolean.ts
index 832a42b..7b42f22 100644
--- a/tests/rules/ionic-attr-type-check-boolean.ts
+++ b/tests/rules/ionic-attr-type-check-boolean.ts
@@ -43,6 +43,18 @@ ruleTester.run('ionic-attr-type-check (boolean attributes)', rule, {
code: '',
filename: 'test.html',
},
+ {
+ code: '',
+ filename: 'test.html',
+ },
+ {
+ code: '',
+ filename: 'test.html',
+ },
+ {
+ code: '',
+ filename: 'test.html',
+ },
// 文字列型属性は文字列値でOK
{
code: '',
@@ -79,6 +91,38 @@ ruleTester.run('ionic-attr-type-check (boolean attributes)', rule, {
},
],
invalid: [
+ {
+ code: '',
+ filename: 'test.html',
+ output: '',
+ errors: [
+ {
+ messageId: 'ionic-attr-type-check',
+ data: {
+ attributeType: 'boolean',
+ attributeName: 'autocorrect',
+ value: 'off',
+ correctValue: 'false',
+ },
+ },
+ ],
+ },
+ {
+ code: '',
+ filename: 'test.html',
+ output: '',
+ errors: [
+ {
+ messageId: 'ionic-attr-type-check',
+ data: {
+ attributeType: 'boolean',
+ attributeName: 'autocorrect',
+ value: 'on',
+ correctValue: 'true',
+ },
+ },
+ ],
+ },
// 文字列値でのboolean属性
{
code: '',
diff --git a/tests/rules/prefer-ionic-standalone.ts b/tests/rules/prefer-ionic-standalone.ts
new file mode 100644
index 0000000..3bee139
--- /dev/null
+++ b/tests/rules/prefer-ionic-standalone.ts
@@ -0,0 +1,90 @@
+import { RuleTester } from '@angular-eslint/test-utils';
+import rule from '../../src/rules/prefer-ionic-standalone';
+
+new RuleTester().run('prefer-ionic-standalone', rule, {
+ valid: [
+ {
+ code: `import { IonButton, ModalController, provideIonicAngular } from '@ionic/angular';`,
+ },
+ {
+ code: `import { Component } from '@angular/core';`,
+ },
+ {
+ code: `import type { IonicConfig } from '@ionic/core';`,
+ },
+ {
+ code: `export { IonButton } from '@ionic/angular';`,
+ },
+ {
+ code: `import * as Ionic from '@ionic/angular'; function read(Ionic: { IonicModule: unknown }) { return Ionic.IonicModule; }`,
+ },
+ ],
+ invalid: [
+ {
+ code: `import { IonButton } from '@ionic/angular/standalone';`,
+ output: `import { IonButton } from '@ionic/angular';`,
+ errors: [{ messageId: 'preferRootEntrypoint', data: { entrypoint: '@ionic/angular/standalone' } }],
+ },
+ {
+ code: `import { IonInput } from '@ionic/angular/lazy';`,
+ output: `import { IonInput } from '@ionic/angular';`,
+ errors: [{ messageId: 'preferRootEntrypoint', data: { entrypoint: '@ionic/angular/lazy' } }],
+ },
+ {
+ code: `import { IonInput } from "@ionic/angular/lazy";`,
+ output: `import { IonInput } from "@ionic/angular";`,
+ errors: [{ messageId: 'preferRootEntrypoint', data: { entrypoint: '@ionic/angular/lazy' } }],
+ },
+ {
+ code: `import '@ionic/angular/lazy';`,
+ output: null,
+ errors: [{ messageId: 'preferRootEntrypoint' }],
+ },
+ {
+ code: `import * as Ionic from '@ionic/angular/lazy';`,
+ output: null,
+ errors: [{ messageId: 'preferRootEntrypoint' }],
+ },
+ {
+ code: `import { IonicModule } from '@ionic/angular';`,
+ errors: [{ messageId: 'noIonicModule' }],
+ },
+ {
+ code: `import { IonicModule as LegacyIonicModule, IonApp } from '@ionic/angular';`,
+ errors: [{ messageId: 'noIonicModule' }],
+ },
+ {
+ code: `import type { IonicModule } from '@ionic/angular';`,
+ errors: [{ messageId: 'noIonicModule' }],
+ },
+ {
+ code: `export { IonButton } from '@ionic/angular/lazy';`,
+ output: `export { IonButton } from '@ionic/angular';`,
+ errors: [{ messageId: 'preferRootEntrypoint', data: { entrypoint: '@ionic/angular/lazy' } }],
+ },
+ {
+ code: `export { IonicModule } from '@ionic/angular';`,
+ errors: [{ messageId: 'noIonicModule' }],
+ },
+ {
+ code: `export * from '@ionic/angular/lazy';`,
+ errors: [{ messageId: 'preferRootEntrypoint' }],
+ },
+ {
+ code: `export * from '@ionic/angular';`,
+ errors: [{ messageId: 'noIonicModule' }],
+ },
+ {
+ code: `export * as Ionic from '@ionic/angular';`,
+ errors: [{ messageId: 'noIonicModule' }],
+ },
+ {
+ code: `import * as Ionic from '@ionic/angular'; Ionic.IonicModule.forRoot();`,
+ errors: [{ messageId: 'noIonicModule' }],
+ },
+ {
+ code: `import * as Ionic from '@ionic/angular'; Ionic['IonicModule'].forRoot();`,
+ errors: [{ messageId: 'noIonicModule' }],
+ },
+ ],
+});
diff --git a/tests/rules/signal-use-as-signal-template-complex.ts b/tests/rules/signal-use-as-signal-template-complex.ts
index 7456357..8aa524a 100644
--- a/tests/rules/signal-use-as-signal-template-complex.ts
+++ b/tests/rules/signal-use-as-signal-template-complex.ts
@@ -58,13 +58,21 @@ new RuleTester().run('signal-use-as-signal-template', rule, {
filename: path.join(__dirname, 'test.component.ts'),
errors: [
{
- message:
- 'Angular Signal count must be called count() to access its value in the templateUrl\ntests/rules/templates/signal-use-as-signal/control-flow-invalid.html:1:5',
+ messageId: 'signalUseAsSignalTemplate',
+ data: {
+ signalName: 'count',
+ signalNameWithParens: 'count()',
+ where: 'templateUrl\ntests/rules/templates/signal-use-as-signal/control-flow-invalid.html:1:5',
+ },
line: 3,
}, // {{ count }}
{
- message:
- 'Angular Signal message must be called message() to access its value in the templateUrl\ntests/rules/templates/signal-use-as-signal/control-flow-invalid.html:2:5',
+ messageId: 'signalUseAsSignalTemplate',
+ data: {
+ signalName: 'message',
+ signalNameWithParens: 'message()',
+ where: 'templateUrl\ntests/rules/templates/signal-use-as-signal/control-flow-invalid.html:2:5',
+ },
line: 3,
}, // {{ message }}
{ messageId: 'signalUseAsSignalTemplate', line: 3 }, // {{ user?.name }}
@@ -74,8 +82,12 @@ new RuleTester().run('signal-use-as-signal-template', rule, {
{ messageId: 'signalUseAsSignalTemplate', line: 3 }, // @switch (status) ...
{ messageId: 'signalUseAsSignalTemplate', line: 3 }, // @if (isAuthenticated) ...
{
- message:
- 'Angular Signal isAdmin must be called isAdmin() to access its value in the templateUrl\ntests/rules/templates/signal-use-as-signal/control-flow-invalid.html:45:0',
+ messageId: 'signalUseAsSignalTemplate',
+ data: {
+ signalName: 'isAdmin',
+ signalNameWithParens: 'isAdmin()',
+ where: 'templateUrl\ntests/rules/templates/signal-use-as-signal/control-flow-invalid.html:45:0',
+ },
line: 3,
}, // @if (isAdmin) ...
{ messageId: 'signalUseAsSignalTemplate', line: 3 }, // hasPermission) ...
diff --git a/tsconfig.json b/tsconfig.json
index de345ce..6fdd641 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -12,7 +12,8 @@
"noFallthroughCasesInSwitch": true,
"resolveJsonModule": true,
"esModuleInterop": true,
- "skipLibCheck": true
+ "skipLibCheck": true,
+ "types": ["node", "jest"]
},
"include": ["src/**/*.ts"],
"exclude": ["node_modules", "scripts"]