@ethlete/eslint-plugin 1.0.0-next.23 → 1.0.0-next.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +26 -0
- package/migrations.json +12 -0
- package/package.json +13 -3
- package/src/configs/recommended.js +33 -11
- package/src/generators/generators.json +10 -0
- package/src/generators/restore-module-augmentation-interfaces/migration.js +50 -0
- package/src/generators/restore-module-augmentation-interfaces/restore-module-augmentation-interfaces.js +103 -0
- package/src/generators/restore-module-augmentation-interfaces/schema.json +14 -0
- package/src/index.js +10 -2
- package/src/rules/angular-decorator-property-order.js +20 -20
- package/src/rules/class-constant-property.js +8 -14
- package/src/rules/class-member-order.js +77 -46
- package/src/rules/consistent-type-definitions.js +60 -0
- package/src/rules/inject-member-accessibility.js +5 -3
- package/src/rules/internals/angular-io.js +12 -13
- package/src/rules/internals/angular-member-visibility.js +11 -7
- package/src/rules/internals/import-resolution.js +191 -0
- package/src/rules/no-angular-router-api.js +23 -29
- package/src/rules/no-angular-seo-services.js +14 -9
- package/src/rules/no-cdk-import.js +7 -14
- package/src/rules/no-csp-unsafe.js +362 -0
- package/src/rules/no-document-cookie.js +3 -0
- package/src/rules/no-effect-cleanup-return.js +7 -3
- package/src/rules/no-empty-angular-metadata-arrays.js +3 -17
- package/src/rules/no-inject-chain.js +3 -1
- package/src/rules/no-leading-underscore-class-member.js +26 -3
- package/src/rules/no-legacy-angular-decorators.js +9 -21
- package/src/rules/no-legacy-query-import.js +6 -15
- package/src/rules/no-locale-id.js +4 -4
- package/src/rules/no-member-alias.js +11 -7
- package/src/rules/no-native-html-input-name.js +1 -1
- package/src/rules/no-pipe-logic.js +10 -17
- package/src/rules/no-redundant-on-push-change-detection.js +9 -23
- package/src/rules/no-rxjs-in-effect.js +12 -8
- package/src/rules/no-screaming-case-local.js +1 -0
- package/src/rules/no-standalone-flag.js +3 -17
- package/src/rules/no-subscribe-in-pipe.js +3 -2
- package/src/rules/no-template-literal-before-inline-template.js +3 -14
- package/src/rules/no-trivial-return-type.js +1 -0
- package/src/rules/no-trivial-wrapper-method.js +10 -16
- package/src/rules/no-typed-injected-element-ref.js +14 -17
- package/src/rules/no-unused-class-member.js +6 -9
- package/src/rules/prefer-clone-equal.js +8 -6
- package/src/rules/prefer-concise-angular-host-directives.js +23 -29
- package/src/rules/prefer-concise-angular-style-metadata.js +8 -16
- package/src/rules/prefer-element-dimensions.js +9 -10
- package/src/rules/prefer-linked-signal.js +42 -41
- package/src/rules/prefer-match-media.js +10 -14
- package/src/rules/prefer-present-tense-output.js +1 -1
- package/src/rules/prefer-rxjs-timer.js +3 -1
- package/src/rules/require-dollar-suffix.js +7 -4
- package/src/rules/require-on-push-change-detection.js +17 -25
- package/src/rules/require-view-encapsulation-none.js +17 -25
- package/src/rules/take-until-destroyed-last.js +64 -0
- package/src/rules/template-member-accessibility.js +8 -6
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.0-next.25
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- The `@angular-eslint/*` output rules moved from `recommendedTs` into a new `recommendedAngularTs` config, which `recommended` includes, so NestJS and other non-Angular projects can use `recommendedTs` without the `@angular-eslint` plugin.
|
|
8
|
+
- `recommendedTs` now uses `ethlete/consistent-type-definitions` instead of `@typescript-eslint/consistent-type-definitions`, so `--fix` no longer turns an interface inside `declare module` or `declare global` (such as the theme-name registry the `@ethlete/core` generators emit) into a type alias that merges into nothing.
|
|
9
|
+
- New `no-csp-unsafe` rule (error in `recommendedTs` and `recommendedTemplate`) flags style attributes, un-nonced `<script>`/`<style>` elements, `eval` and string timers that a strict CSP blocks.
|
|
10
|
+
- `et update` turns the type aliases the old `consistent-type-definitions` fix left in `declare module` and `declare global` blocks back into interfaces, and renames the rule in `eslint-disable` directives.
|
|
11
|
+
- `prefer-linked-signal` now also reports an `effect()` that only calls `.set()` behind an `if`/`else`.
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- `ethlete/no-csp-unsafe` fixes a static template `style="…"` attribute. It rewrites `style="width: 40%; margin-top: 4px"` to `[style]="{ width: '40%', 'margin-top': '4px' }"`, which a strict CSP allows. A value with a quote, `url(`, `!important` or a comment stays a manual fix.
|
|
16
|
+
|
|
17
|
+
## 1.0.0-next.24
|
|
18
|
+
|
|
19
|
+
### Minor Changes
|
|
20
|
+
|
|
21
|
+
- Add `take-until-destroyed-last` to the recommended config: `takeUntilDestroyed()` must be the last operator in a `.pipe()`, since an operator after it can keep the subscription alive.
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- `class-member-order` now fixes classes with `static` members or index signatures and keeps a trailing comment with its member, and `prefer-concise-angular-host-directives` no longer drops or misplaces comments.
|
|
26
|
+
- Angular and RxJS rules now resolve `Component`, `inject`, `input`, `effect` and friends through their imports, `document` and `setTimeout` match only the globals, and import bans also report re-exports and dynamic imports.
|
|
27
|
+
- Several fixers no longer emit broken code (a dangling `other._value`, a duplicate `styleUrl`, a dropped `host` spread), `no-trivial-return-type` checks function declarations, `no-screaming-case-local` reports a function once, and `standalone` sorts after `selector`.
|
|
28
|
+
|
|
3
29
|
## 1.0.0-next.23
|
|
4
30
|
|
|
5
31
|
### Minor Changes
|
package/migrations.json
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"migrations": [
|
|
3
|
+
{
|
|
4
|
+
"name": "module-augmentation-interfaces",
|
|
5
|
+
"version": "1.0.0-next.25",
|
|
6
|
+
"kind": "auto",
|
|
7
|
+
"description": "Turn the type aliases the old consistent-type-definitions fix left in declare module / declare global blocks back into interfaces, and rename its eslint-disable directives to ethlete/consistent-type-definitions.",
|
|
8
|
+
"generator": "@ethlete/eslint-plugin:restore-module-augmentation-interfaces",
|
|
9
|
+
"docs": "/eslint/rules"
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ethlete/eslint-plugin",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.25",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
7
|
"peerDependencies": {
|
|
8
8
|
"@angular-eslint/eslint-plugin-template": ">=21.0.0",
|
|
9
9
|
"@angular/core": ">=21.0.0",
|
|
10
|
+
"@nx/devkit": ">=21.0.0",
|
|
10
11
|
"@typescript-eslint/eslint-plugin": ">=8.0.0",
|
|
11
12
|
"eslint": ">=9.0.0",
|
|
12
13
|
"typescript": ">=5.9.0"
|
|
13
|
-
}
|
|
14
|
-
|
|
14
|
+
},
|
|
15
|
+
"peerDependenciesMeta": {
|
|
16
|
+
"@nx/devkit": {
|
|
17
|
+
"optional": true
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"ethlete": {
|
|
21
|
+
"migrations": "./migrations.json"
|
|
22
|
+
},
|
|
23
|
+
"generators": "./src/generators/generators.json"
|
|
24
|
+
}
|
|
@@ -15,8 +15,8 @@ const recommendedTs = {
|
|
|
15
15
|
rules: {
|
|
16
16
|
// ── TypeScript ──────────────────────────────────────────────────────────
|
|
17
17
|
|
|
18
|
-
// No interface — use type
|
|
19
|
-
'@typescript-eslint/consistent-type-definitions':
|
|
18
|
+
// No interface — use type (owned by ethlete/consistent-type-definitions)
|
|
19
|
+
'@typescript-eslint/consistent-type-definitions': 'off',
|
|
20
20
|
|
|
21
21
|
// No any
|
|
22
22
|
'@typescript-eslint/no-explicit-any': 'error',
|
|
@@ -239,12 +239,18 @@ const recommendedTs = {
|
|
|
239
239
|
// No .subscribe() inside a .pipe() callback
|
|
240
240
|
'ethlete/no-subscribe-in-pipe': 'error',
|
|
241
241
|
|
|
242
|
+
// takeUntilDestroyed() is the last operator in a .pipe()
|
|
243
|
+
'ethlete/take-until-destroyed-last': 'error',
|
|
244
|
+
|
|
242
245
|
// No async/await — asynchronous work is modelled as cold Observables
|
|
243
246
|
'ethlete/no-async-await': 'error',
|
|
244
247
|
|
|
245
248
|
// No enum / const enum — use a const object with `as const` plus a derived union type
|
|
246
249
|
'ethlete/no-enum': 'error',
|
|
247
250
|
|
|
251
|
+
// No interface — use type; an interface augmenting a module or the global scope is exempt
|
|
252
|
+
'ethlete/consistent-type-definitions': 'error',
|
|
253
|
+
|
|
248
254
|
// No .subscribe() inside effect() or computed()
|
|
249
255
|
'ethlete/no-rxjs-in-effect': 'error',
|
|
250
256
|
|
|
@@ -308,6 +314,10 @@ const recommendedTs = {
|
|
|
308
314
|
// — use getCookie / setCookie / hasCookie / deleteCookie from '@ethlete/core' instead
|
|
309
315
|
'ethlete/no-document-cookie': 'error',
|
|
310
316
|
|
|
317
|
+
// No code a strict CSP (no 'unsafe-inline' / 'unsafe-eval') blocks: style attributes,
|
|
318
|
+
// un-nonced <script>/<style> elements, eval / new Function / string timers
|
|
319
|
+
'ethlete/no-csp-unsafe': 'error',
|
|
320
|
+
|
|
311
321
|
// Injected providers must be private by default, and protected only when
|
|
312
322
|
// referenced from an Angular template or host binding.
|
|
313
323
|
'ethlete/inject-member-accessibility': 'error',
|
|
@@ -347,14 +357,6 @@ const recommendedTs = {
|
|
|
347
357
|
// Angular language service completions for the rest of the file
|
|
348
358
|
'ethlete/no-template-literal-before-inline-template': 'error',
|
|
349
359
|
|
|
350
|
-
// ── Angular outputs ─────────────────────────────────────────────────────
|
|
351
|
-
|
|
352
|
-
// No on-prefixed outputs (onSelectDate → selectDate)
|
|
353
|
-
'@angular-eslint/no-output-on-prefix': 'error',
|
|
354
|
-
|
|
355
|
-
// No outputs named after native DOM events (change, click, etc.)
|
|
356
|
-
'@angular-eslint/no-output-native': 'error',
|
|
357
|
-
|
|
358
360
|
// ── Angular components ──────────────────────────────────────────────────
|
|
359
361
|
|
|
360
362
|
// Require ChangeDetectionStrategy.OnPush on Angular <= 21, where it is opt-in
|
|
@@ -399,6 +401,23 @@ const recommendedTs = {
|
|
|
399
401
|
},
|
|
400
402
|
};
|
|
401
403
|
|
|
404
|
+
/**
|
|
405
|
+
* `@angular-eslint/*` rules for TypeScript files in Angular projects; the base config must register
|
|
406
|
+
* the `@angular-eslint` plugin.
|
|
407
|
+
*
|
|
408
|
+
* @type {import('eslint').Linter.Config}
|
|
409
|
+
*/
|
|
410
|
+
const recommendedAngularTs = {
|
|
411
|
+
files: ['**/*.ts'],
|
|
412
|
+
rules: {
|
|
413
|
+
// No on-prefixed outputs (onSelectDate → selectDate)
|
|
414
|
+
'@angular-eslint/no-output-on-prefix': 'error',
|
|
415
|
+
|
|
416
|
+
// No outputs named after native DOM events (change, click, etc.)
|
|
417
|
+
'@angular-eslint/no-output-native': 'error',
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
|
|
402
421
|
/**
|
|
403
422
|
* Flat config entries for Angular HTML template files.
|
|
404
423
|
* @type {import('eslint').Linter.Config}
|
|
@@ -416,6 +435,8 @@ const recommendedTemplate = {
|
|
|
416
435
|
'ethlete/prefer-static-boolean-properties': 'warn',
|
|
417
436
|
// A form must handle its own submission
|
|
418
437
|
'ethlete/require-form-submit': 'error',
|
|
438
|
+
// No static style attribute or [attr.style] binding - a strict CSP blocks both
|
|
439
|
+
'ethlete/no-csp-unsafe': 'error',
|
|
419
440
|
},
|
|
420
441
|
};
|
|
421
442
|
|
|
@@ -429,6 +450,7 @@ const recommendedSpec = {
|
|
|
429
450
|
'@typescript-eslint/no-non-null-assertion': 'off',
|
|
430
451
|
'no-restricted-globals': 'off',
|
|
431
452
|
'ethlete/no-async-await': 'off',
|
|
453
|
+
'ethlete/no-csp-unsafe': 'off',
|
|
432
454
|
'ethlete/no-direct-dom-manipulation': 'off',
|
|
433
455
|
'ethlete/no-document-cookie': 'off',
|
|
434
456
|
'ethlete/no-dom-query': 'off',
|
|
@@ -441,4 +463,4 @@ const recommendedSpec = {
|
|
|
441
463
|
},
|
|
442
464
|
};
|
|
443
465
|
|
|
444
|
-
module.exports = { recommendedTs, recommendedTemplate, recommendedSpec };
|
|
466
|
+
module.exports = { recommendedTs, recommendedAngularTs, recommendedTemplate, recommendedSpec };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/nx-generators",
|
|
3
|
+
"generators": {
|
|
4
|
+
"restore-module-augmentation-interfaces": {
|
|
5
|
+
"implementation": "./restore-module-augmentation-interfaces/migration",
|
|
6
|
+
"schema": "./restore-module-augmentation-interfaces/schema.json",
|
|
7
|
+
"description": "Turn type aliases in declare module / declare global blocks back into interfaces, and rename consistent-type-definitions eslint directives"
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { formatFiles, visitNotIgnoredFiles } = require('@nx/devkit');
|
|
5
|
+
const { restoreModuleAugmentationInterfaces } = require('./restore-module-augmentation-interfaces');
|
|
6
|
+
|
|
7
|
+
const SOURCE_FILE = /\.(?:[cm]?ts|tsx|[cm]?js|jsx)$/;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @param {import('@nx/devkit').Tree} tree
|
|
11
|
+
* @param {{ skipFormat?: boolean }} schema
|
|
12
|
+
*/
|
|
13
|
+
const migrateModuleAugmentationInterfaces = async (tree, schema) => {
|
|
14
|
+
console.log('\n🔄 Restoring module augmentation interfaces...');
|
|
15
|
+
|
|
16
|
+
let filesChanged = 0;
|
|
17
|
+
let reviewCount = 0;
|
|
18
|
+
|
|
19
|
+
visitNotIgnoredFiles(tree, '', (filePath) => {
|
|
20
|
+
if (!SOURCE_FILE.test(filePath)) return;
|
|
21
|
+
|
|
22
|
+
const before = tree.read(filePath, 'utf-8');
|
|
23
|
+
|
|
24
|
+
if (!before) return;
|
|
25
|
+
|
|
26
|
+
const result = restoreModuleAugmentationInterfaces(filePath, before);
|
|
27
|
+
|
|
28
|
+
for (const entry of result.review) {
|
|
29
|
+
reviewCount++;
|
|
30
|
+
console.warn(
|
|
31
|
+
` ⚠ ${filePath}:${entry.line}: type ${entry.name} in a module augmentation is not an object literal, so it was left alone. An augmentation only merges as an interface.`,
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!result.changed) return;
|
|
36
|
+
|
|
37
|
+
tree.write(filePath, result.content);
|
|
38
|
+
filesChanged++;
|
|
39
|
+
console.log(` ✓ ${filePath}`);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
console.log(filesChanged === 0 ? ' Nothing to migrate.\n' : `\n✅ Migrated ${filesChanged} file(s).\n`);
|
|
43
|
+
|
|
44
|
+
if (reviewCount > 0) console.warn(` Review ${reviewCount} type alias(es) in module augmentations manually.\n`);
|
|
45
|
+
|
|
46
|
+
if (!schema.skipFormat) await formatFiles(tree);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
module.exports = migrateModuleAugmentationInterfaces;
|
|
50
|
+
module.exports.default = migrateModuleAugmentationInterfaces;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const ts = require('typescript');
|
|
5
|
+
|
|
6
|
+
const OLD_RULE = '@typescript-eslint/consistent-type-definitions';
|
|
7
|
+
const NEW_RULE = 'ethlete/consistent-type-definitions';
|
|
8
|
+
|
|
9
|
+
const LINE_DIRECTIVE = /\/\/[ \t]*eslint-(?:disable|enable)(?:-next-line|-line)?\b[^\n]*/g;
|
|
10
|
+
const BLOCK_DIRECTIVE = /\/\*[ \t]*eslint-(?:disable|enable)(?:-next-line|-line)?\b(?:(?!\*\/)[\s\S])*\*\//g;
|
|
11
|
+
|
|
12
|
+
const AUGMENTATION = /\bdeclare\s+(?:module|global)\b/;
|
|
13
|
+
|
|
14
|
+
const renameInDirective = (/** @type {string} */ directive) => directive.split(OLD_RULE).join(NEW_RULE);
|
|
15
|
+
|
|
16
|
+
/** Renames `@typescript-eslint/consistent-type-definitions` in every eslint-disable/enable directive. */
|
|
17
|
+
const renameDirectives = (/** @type {string} */ content) =>
|
|
18
|
+
content.replace(LINE_DIRECTIVE, renameInDirective).replace(BLOCK_DIRECTIVE, renameInDirective);
|
|
19
|
+
|
|
20
|
+
const isAugmentation = (/** @type {ts.Statement} */ statement) =>
|
|
21
|
+
ts.isModuleDeclaration(statement) &&
|
|
22
|
+
(ts.isStringLiteral(statement.name) || (statement.flags & ts.NodeFlags.GlobalAugmentation) !== 0) &&
|
|
23
|
+
statement.body !== undefined &&
|
|
24
|
+
ts.isModuleBlock(statement.body);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {readonly ts.Statement[]} statements
|
|
28
|
+
* @param {ts.TypeAliasDeclaration[]} found
|
|
29
|
+
*/
|
|
30
|
+
const collectAugmentationAliases = (statements, found) => {
|
|
31
|
+
for (const statement of statements) {
|
|
32
|
+
if (!isAugmentation(statement)) continue;
|
|
33
|
+
|
|
34
|
+
const body = /** @type {ts.ModuleBlock} */ (/** @type {ts.ModuleDeclaration} */ (statement).body);
|
|
35
|
+
|
|
36
|
+
for (const member of body.statements) {
|
|
37
|
+
if (ts.isTypeAliasDeclaration(member)) found.push(member);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
collectAugmentationAliases(body.statements, found);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return found;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Turns every object-literal `type X = { … }` directly inside a `declare module '…'` or
|
|
48
|
+
* `declare global` block back into `interface X { … }`, and renames
|
|
49
|
+
* `@typescript-eslint/consistent-type-definitions` in eslint directives to
|
|
50
|
+
* `ethlete/consistent-type-definitions`. Aliases of any other shape are returned in `review`.
|
|
51
|
+
*
|
|
52
|
+
* @param {string} filePath
|
|
53
|
+
* @param {string} content
|
|
54
|
+
* @returns {{ content: string; changed: boolean; review: { name: string; line: number }[] }}
|
|
55
|
+
*/
|
|
56
|
+
const restoreModuleAugmentationInterfaces = (filePath, content) => {
|
|
57
|
+
if (!AUGMENTATION.test(content)) {
|
|
58
|
+
const renamed = renameDirectives(content);
|
|
59
|
+
|
|
60
|
+
return { content: renamed, changed: renamed !== content, review: [] };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
64
|
+
const aliases = collectAugmentationAliases(sourceFile.statements, []);
|
|
65
|
+
|
|
66
|
+
/** @type {{ name: string; line: number }[]} */
|
|
67
|
+
const review = [];
|
|
68
|
+
/** @type {{ start: number; end: number; text: string }[]} */
|
|
69
|
+
const edits = [];
|
|
70
|
+
|
|
71
|
+
for (const alias of aliases) {
|
|
72
|
+
const name = alias.name.text;
|
|
73
|
+
|
|
74
|
+
if (!ts.isTypeLiteralNode(alias.type)) {
|
|
75
|
+
review.push({ name, line: sourceFile.getLineAndCharacterOfPosition(alias.getStart(sourceFile)).line + 1 });
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const modifiers = (alias.modifiers ?? []).map((modifier) => `${modifier.getText(sourceFile)} `).join('');
|
|
80
|
+
const typeParameters = content
|
|
81
|
+
.slice(alias.name.end, alias.type.pos)
|
|
82
|
+
.replace(/\s*=\s*$/, '')
|
|
83
|
+
.trim();
|
|
84
|
+
|
|
85
|
+
edits.push({
|
|
86
|
+
start: alias.modifiers?.[0]?.getStart(sourceFile) ?? alias.getStart(sourceFile),
|
|
87
|
+
end: alias.end,
|
|
88
|
+
text: `${modifiers}interface ${name}${typeParameters} ${alias.type.getText(sourceFile)}`,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
let next = content;
|
|
93
|
+
|
|
94
|
+
for (const edit of edits.sort((left, right) => right.start - left.start)) {
|
|
95
|
+
next = next.slice(0, edit.start) + edit.text + next.slice(edit.end);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
next = renameDirectives(next);
|
|
99
|
+
|
|
100
|
+
return { content: next, changed: next !== content, review };
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
module.exports = { restoreModuleAugmentationInterfaces };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/schema",
|
|
3
|
+
"$id": "restore-module-augmentation-interfaces",
|
|
4
|
+
"title": "Restore module augmentation interfaces",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"properties": {
|
|
7
|
+
"skipFormat": {
|
|
8
|
+
"type": "boolean",
|
|
9
|
+
"description": "Skip formatting files after migration",
|
|
10
|
+
"default": false
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"required": []
|
|
14
|
+
}
|
package/src/index.js
CHANGED
|
@@ -30,6 +30,7 @@ const noLegacyAngularDecorators = require('./rules/no-legacy-angular-decorators'
|
|
|
30
30
|
const noAngularSeoServices = require('./rules/no-angular-seo-services');
|
|
31
31
|
const preferCloneEqual = require('./rules/prefer-clone-equal');
|
|
32
32
|
const noDocumentCookie = require('./rules/no-document-cookie');
|
|
33
|
+
const noCspUnsafe = require('./rules/no-csp-unsafe');
|
|
33
34
|
const injectMemberAccessibility = require('./rules/inject-member-accessibility');
|
|
34
35
|
const noRedundantInternal = require('./rules/no-redundant-internal');
|
|
35
36
|
const templateMemberAccessibility = require('./rules/template-member-accessibility');
|
|
@@ -60,7 +61,9 @@ const noCdkImport = require('./rules/no-cdk-import');
|
|
|
60
61
|
const noLegacyQueryImport = require('./rules/no-legacy-query-import');
|
|
61
62
|
const noAsyncAwait = require('./rules/no-async-await');
|
|
62
63
|
const noEnum = require('./rules/no-enum');
|
|
63
|
-
const
|
|
64
|
+
const consistentTypeDefinitions = require('./rules/consistent-type-definitions');
|
|
65
|
+
const takeUntilDestroyedLast = require('./rules/take-until-destroyed-last');
|
|
66
|
+
const { recommendedTs, recommendedAngularTs, recommendedTemplate, recommendedSpec } = require('./configs/recommended');
|
|
64
67
|
const { version } = require('../package.json');
|
|
65
68
|
|
|
66
69
|
/** @type {import('eslint').ESLint.Plugin} */
|
|
@@ -74,6 +77,7 @@ const plugin = {
|
|
|
74
77
|
'no-legacy-prepare-without-injector': noLegacyPrepareWithoutInjector,
|
|
75
78
|
'no-trivial-return-type': noTrivialReturnType,
|
|
76
79
|
'no-subscribe-in-pipe': noSubscribeInPipe,
|
|
80
|
+
'take-until-destroyed-last': takeUntilDestroyedLast,
|
|
77
81
|
'no-rxjs-in-effect': noRxjsInEffect,
|
|
78
82
|
'no-effect-cleanup-return': noEffectCleanupReturn,
|
|
79
83
|
'no-readonly-signal': noReadonlySignal,
|
|
@@ -100,6 +104,7 @@ const plugin = {
|
|
|
100
104
|
'no-angular-seo-services': noAngularSeoServices,
|
|
101
105
|
'prefer-clone-equal': preferCloneEqual,
|
|
102
106
|
'no-document-cookie': noDocumentCookie,
|
|
107
|
+
'no-csp-unsafe': noCspUnsafe,
|
|
103
108
|
'inject-member-accessibility': injectMemberAccessibility,
|
|
104
109
|
'no-redundant-internal': noRedundantInternal,
|
|
105
110
|
'template-member-accessibility': templateMemberAccessibility,
|
|
@@ -129,6 +134,7 @@ const plugin = {
|
|
|
129
134
|
'no-legacy-query-import': noLegacyQueryImport,
|
|
130
135
|
'no-async-await': noAsyncAwait,
|
|
131
136
|
'no-enum': noEnum,
|
|
137
|
+
'consistent-type-definitions': consistentTypeDefinitions,
|
|
132
138
|
},
|
|
133
139
|
};
|
|
134
140
|
|
|
@@ -148,6 +154,8 @@ const recommendedTemplateWithPlugin = {
|
|
|
148
154
|
const configs = {
|
|
149
155
|
/** TypeScript rules (merged into a files: ['**\/*.ts'] block) */
|
|
150
156
|
recommendedTs: recommendedTsWithPlugin,
|
|
157
|
+
/** `@angular-eslint/*` rules for TypeScript files (files: ['**\/*.ts']) */
|
|
158
|
+
recommendedAngularTs,
|
|
151
159
|
/** Angular template rules (merged into a files: ['**\/*.html'] block) */
|
|
152
160
|
recommendedTemplate: recommendedTemplateWithPlugin,
|
|
153
161
|
/** Relaxed rules for spec files (files: ['**\/*.spec.ts']) */
|
|
@@ -156,7 +164,7 @@ const configs = {
|
|
|
156
164
|
* Both together as a flat array — the most common usage:
|
|
157
165
|
* export default [...baseConfig, ...ethlete.configs.recommended]
|
|
158
166
|
*/
|
|
159
|
-
recommended: [recommendedTsWithPlugin, recommendedTemplateWithPlugin, recommendedSpec],
|
|
167
|
+
recommended: [recommendedTsWithPlugin, recommendedAngularTs, recommendedTemplateWithPlugin, recommendedSpec],
|
|
160
168
|
};
|
|
161
169
|
|
|
162
170
|
const ethletePlugin = { ...plugin, configs };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
const { getAngularDecoratorName } = require('./internals/import-resolution');
|
|
5
|
+
|
|
4
6
|
/** @typedef {'Component' | 'Directive'} TDecoratorName */
|
|
5
7
|
/** @typedef {{ order: string[]; orderText: string }} TDecoratorConfig */
|
|
6
8
|
/** @typedef {{ range: [number, number] }} TNodeWithRange */
|
|
@@ -14,11 +16,13 @@ const DECORATOR_CONFIG = {
|
|
|
14
16
|
Component: {
|
|
15
17
|
order: [
|
|
16
18
|
'selector',
|
|
19
|
+
'standalone',
|
|
17
20
|
'template',
|
|
18
21
|
'styleUrl',
|
|
19
22
|
'encapsulation',
|
|
20
23
|
'changeDetection',
|
|
21
24
|
'imports',
|
|
25
|
+
'deferredImports',
|
|
22
26
|
'providers',
|
|
23
27
|
'viewProviders',
|
|
24
28
|
'animations',
|
|
@@ -29,16 +33,28 @@ const DECORATOR_CONFIG = {
|
|
|
29
33
|
'outputs',
|
|
30
34
|
'queries',
|
|
31
35
|
'preserveWhitespaces',
|
|
36
|
+
'interpolation',
|
|
32
37
|
'schemas',
|
|
33
38
|
'jit',
|
|
34
39
|
'exportAs',
|
|
35
40
|
],
|
|
36
41
|
orderText:
|
|
37
|
-
'selector, template/templateUrl, styleUrl/styleUrls, encapsulation, changeDetection, imports, providers, viewProviders, animations, hostDirectives, host, styles, inputs, outputs, queries, preserveWhitespaces, schemas, jit, exportAs',
|
|
42
|
+
'selector, standalone, template/templateUrl, styleUrl/styleUrls, encapsulation, changeDetection, imports, deferredImports, providers, viewProviders, animations, hostDirectives, host, styles, inputs, outputs, queries, preserveWhitespaces, interpolation, schemas, jit, exportAs',
|
|
38
43
|
},
|
|
39
44
|
Directive: {
|
|
40
|
-
order: [
|
|
41
|
-
|
|
45
|
+
order: [
|
|
46
|
+
'selector',
|
|
47
|
+
'standalone',
|
|
48
|
+
'exportAs',
|
|
49
|
+
'providers',
|
|
50
|
+
'inputs',
|
|
51
|
+
'outputs',
|
|
52
|
+
'queries',
|
|
53
|
+
'hostDirectives',
|
|
54
|
+
'host',
|
|
55
|
+
'jit',
|
|
56
|
+
],
|
|
57
|
+
orderText: 'selector, standalone, exportAs, providers, inputs, outputs, queries, hostDirectives, host, jit',
|
|
42
58
|
},
|
|
43
59
|
};
|
|
44
60
|
|
|
@@ -66,22 +82,6 @@ const getOrderKey = (decoratorName, propertyName) => {
|
|
|
66
82
|
return propertyName;
|
|
67
83
|
};
|
|
68
84
|
|
|
69
|
-
/**
|
|
70
|
-
* @param {import('eslint').Rule.Node} node
|
|
71
|
-
* @returns {TDecoratorName | null}
|
|
72
|
-
*/
|
|
73
|
-
const getDecoratorName = (node) => {
|
|
74
|
-
const decorator = /** @type {any} */ (node);
|
|
75
|
-
if (decorator.type !== 'Decorator') return null;
|
|
76
|
-
|
|
77
|
-
const expression = decorator.expression;
|
|
78
|
-
if (expression.type === 'CallExpression') {
|
|
79
|
-
return expression.callee.type === 'Identifier' ? expression.callee.name : null;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
return expression.type === 'Identifier' ? expression.name : null;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
85
|
/** @type {import('eslint').Rule.RuleModule} */
|
|
86
86
|
const angularDecoratorPropertyOrder = {
|
|
87
87
|
meta: {
|
|
@@ -101,7 +101,7 @@ const angularDecoratorPropertyOrder = {
|
|
|
101
101
|
return {
|
|
102
102
|
/** @param {import('eslint').Rule.Node} node */
|
|
103
103
|
Decorator(node) {
|
|
104
|
-
const decoratorName =
|
|
104
|
+
const decoratorName = getAngularDecoratorName(context.sourceCode, node);
|
|
105
105
|
if (decoratorName !== 'Component' && decoratorName !== 'Directive') return;
|
|
106
106
|
|
|
107
107
|
const decorator = /** @type {any} */ (node);
|
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
const { getAngularDecoratorName } = require('./internals/import-resolution');
|
|
5
|
+
|
|
4
6
|
const SCREAMING_CASE_RE = /^[A-Z][A-Z0-9_]*$/;
|
|
5
7
|
|
|
6
8
|
/**
|
|
9
|
+
* @param {import('eslint').SourceCode} sourceCode
|
|
7
10
|
* @param {any} classNode
|
|
8
11
|
*/
|
|
9
|
-
const hasPipeDecorator = (classNode) =>
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
if (expr.type === 'CallExpression') {
|
|
14
|
-
return expr.callee.type === 'Identifier' && expr.callee.name === 'Pipe';
|
|
15
|
-
}
|
|
16
|
-
if (expr.type === 'Identifier') {
|
|
17
|
-
return expr.name === 'Pipe';
|
|
18
|
-
}
|
|
19
|
-
return false;
|
|
20
|
-
});
|
|
21
|
-
};
|
|
12
|
+
const hasPipeDecorator = (sourceCode, classNode) =>
|
|
13
|
+
(classNode.decorators ?? []).some(
|
|
14
|
+
(/** @type {any} */ decorator) => getAngularDecoratorName(sourceCode, decorator) === 'Pipe',
|
|
15
|
+
);
|
|
22
16
|
|
|
23
17
|
const REACTIVE_APIS = new Set([
|
|
24
18
|
'signal',
|
|
@@ -353,7 +347,7 @@ const classConstantProperty = {
|
|
|
353
347
|
continue;
|
|
354
348
|
}
|
|
355
349
|
|
|
356
|
-
if (memberName === 'transform' && hasPipeDecorator(node.parent)) {
|
|
350
|
+
if (memberName === 'transform' && hasPipeDecorator(context.sourceCode, node.parent)) {
|
|
357
351
|
continue;
|
|
358
352
|
}
|
|
359
353
|
|