diff --git a/packages/eslint-plugin/docs/rules/no-output-rename.md b/packages/eslint-plugin/docs/rules/no-output-rename.md
index 733aa2a79..ac8617f3a 100644
--- a/packages/eslint-plugin/docs/rules/no-output-rename.md
+++ b/packages/eslint-plugin/docs/rules/no-output-rename.md
@@ -725,6 +725,105 @@ class Test {
#### ✅ Valid Code
+```ts
+@Component({
+ selector: 'foo',
+ hostDirectives: [{
+ directive: CdkMenuItem,
+ outputs: ['cdkMenuItemTriggered: triggered'],
+ }]
+})
+class Test {}
+```
+
+
+
+---
+
+
+
+#### Default Config
+
+```json
+{
+ "rules": {
+ "@angular-eslint/no-output-rename": [
+ "error"
+ ]
+ }
+}
+```
+
+
+
+#### ✅ Valid Code
+
+```ts
+@Component({
+ selector: 'foo',
+ 'hostDirectives': [{
+ directive: CdkMenuItem,
+ outputs: ['cdkMenuItemTriggered: triggered'],
+ }]
+})
+class Test {}
+```
+
+
+
+---
+
+
+
+#### Default Config
+
+```json
+{
+ "rules": {
+ "@angular-eslint/no-output-rename": [
+ "error"
+ ]
+ }
+}
+```
+
+
+
+#### ✅ Valid Code
+
+```ts
+@Component({
+ selector: 'foo',
+ ['hostDirectives']: [{
+ directive: CdkMenuItem,
+ outputs: ['cdkMenuItemTriggered: triggered'],
+ }]
+})
+class Test {}
+```
+
+
+
+---
+
+
+
+#### Default Config
+
+```json
+{
+ "rules": {
+ "@angular-eslint/no-output-rename": [
+ "error"
+ ]
+ }
+}
+```
+
+
+
+#### ✅ Valid Code
+
```ts
@Directive({
selector: 'foo'
diff --git a/packages/eslint-plugin/docs/rules/no-outputs-metadata-property.md b/packages/eslint-plugin/docs/rules/no-outputs-metadata-property.md
index e8b5b73a4..600025b9c 100644
--- a/packages/eslint-plugin/docs/rules/no-outputs-metadata-property.md
+++ b/packages/eslint-plugin/docs/rules/no-outputs-metadata-property.md
@@ -489,6 +489,105 @@ class Test {}
class Test {}
```
+
+
+---
+
+
+
+#### Default Config
+
+```json
+{
+ "rules": {
+ "@angular-eslint/no-outputs-metadata-property": [
+ "error"
+ ]
+ }
+}
+```
+
+
+
+#### ✅ Valid Code
+
+```ts
+@Component({
+ selector: 'foo',
+ hostDirectives: [{
+ directive: CdkMenuItem,
+ outputs: ['cdkMenuItemTriggered: triggered'],
+ }]
+})
+class Test {}
+```
+
+
+
+---
+
+
+
+#### Default Config
+
+```json
+{
+ "rules": {
+ "@angular-eslint/no-outputs-metadata-property": [
+ "error"
+ ]
+ }
+}
+```
+
+
+
+#### ✅ Valid Code
+
+```ts
+@Component({
+ selector: 'foo',
+ 'hostDirectives': [{
+ directive: CdkMenuItem,
+ outputs: ['cdkMenuItemTriggered: triggered'],
+ }]
+})
+class Test {}
+```
+
+
+
+---
+
+
+
+#### Default Config
+
+```json
+{
+ "rules": {
+ "@angular-eslint/no-outputs-metadata-property": [
+ "error"
+ ]
+ }
+}
+```
+
+
+
+#### ✅ Valid Code
+
+```ts
+@Component({
+ selector: 'foo',
+ ['hostDirectives']: [{
+ directive: CdkMenuItem,
+ outputs: ['cdkMenuItemTriggered: triggered'],
+ }]
+})
+class Test {}
+```
+
diff --git a/packages/eslint-plugin/src/rules/no-output-rename.ts b/packages/eslint-plugin/src/rules/no-output-rename.ts
index 23f71258a..6d446d085 100644
--- a/packages/eslint-plugin/src/rules/no-output-rename.ts
+++ b/packages/eslint-plugin/src/rules/no-output-rename.ts
@@ -98,6 +98,29 @@ export default createESLintRule({
[Selectors.OUTPUTS_METADATA_PROPERTY_LITERAL](
node: TSESTree.Literal | TSESTree.TemplateElement,
) {
+ const ancestorMaybeHostDirectiveAPI =
+ node.parent?.parent?.parent?.parent?.parent;
+ if (
+ ancestorMaybeHostDirectiveAPI &&
+ ASTUtils.isProperty(ancestorMaybeHostDirectiveAPI)
+ ) {
+ /**
+ * Angular v15 introduced the directive composition API: https://angular.io/guide/directive-composition-api
+ * Renaming host directive outputs using this API is not a bad practice and should not be reported
+ */
+ const hostDirectiveAPIPropertyName = 'hostDirectives';
+ if (
+ (ASTUtils.isLiteral(ancestorMaybeHostDirectiveAPI.key) &&
+ ancestorMaybeHostDirectiveAPI.key.value ===
+ hostDirectiveAPIPropertyName) ||
+ (TSESLintASTUtils.isIdentifier(ancestorMaybeHostDirectiveAPI.key) &&
+ ancestorMaybeHostDirectiveAPI.key.name ===
+ hostDirectiveAPIPropertyName)
+ ) {
+ return;
+ }
+ }
+
const [propertyName, aliasName] = withoutBracketsAndWhitespaces(
ASTUtils.getRawText(node),
).split(':');
diff --git a/packages/eslint-plugin/src/rules/no-outputs-metadata-property.ts b/packages/eslint-plugin/src/rules/no-outputs-metadata-property.ts
index 881ccd5be..98f7f15c1 100644
--- a/packages/eslint-plugin/src/rules/no-outputs-metadata-property.ts
+++ b/packages/eslint-plugin/src/rules/no-outputs-metadata-property.ts
@@ -1,5 +1,6 @@
-import { Selectors } from '@angular-eslint/utils';
+import { ASTUtils, Selectors } from '@angular-eslint/utils';
import type { TSESTree } from '@typescript-eslint/utils';
+import { ASTUtils as TSESLintASTUtils } from '@typescript-eslint/utils';
import { createESLintRule } from '../utils/create-eslint-rule';
type Options = [];
@@ -29,6 +30,30 @@ export default createESLintRule({
} ${Selectors.metadataProperty(METADATA_PROPERTY_NAME)}`](
node: TSESTree.Property,
) {
+ /**
+ * Angular v15 introduced the directive composition API: https://angular.io/guide/directive-composition-api
+ * Using host directive outputs using this API is not a bad practice and should not be reported
+ */
+ const ancestorMayBeHostDirectiveAPI = node.parent?.parent?.parent;
+
+ if (
+ ancestorMayBeHostDirectiveAPI &&
+ ASTUtils.isProperty(ancestorMayBeHostDirectiveAPI)
+ ) {
+ const hostDirectiveAPIPropertyName = 'hostDirectives';
+
+ if (
+ (ASTUtils.isLiteral(ancestorMayBeHostDirectiveAPI.key) &&
+ ancestorMayBeHostDirectiveAPI.key.value ===
+ hostDirectiveAPIPropertyName) ||
+ (TSESLintASTUtils.isIdentifier(ancestorMayBeHostDirectiveAPI.key) &&
+ ancestorMayBeHostDirectiveAPI.key.name ===
+ hostDirectiveAPIPropertyName)
+ ) {
+ return;
+ }
+ }
+
context.report({
node,
messageId: 'noOutputsMetadataProperty',
diff --git a/packages/eslint-plugin/tests/rules/no-output-rename/cases.ts b/packages/eslint-plugin/tests/rules/no-output-rename/cases.ts
index fc9539e3f..692e0a166 100644
--- a/packages/eslint-plugin/tests/rules/no-output-rename/cases.ts
+++ b/packages/eslint-plugin/tests/rules/no-output-rename/cases.ts
@@ -108,6 +108,40 @@ export const valid = [
@Output('foo') label: string;
}
`,
+ /**
+ * Renaming outputs when using the directive composition API is not a bad practice
+ * https://angular.io/guide/directive-composition-api
+ */
+ `
+ @Component({
+ selector: 'foo',
+ hostDirectives: [{
+ directive: CdkMenuItem,
+ outputs: ['cdkMenuItemTriggered: triggered'],
+ }]
+ })
+ class Test {}
+ `,
+ `
+ @Component({
+ selector: 'foo',
+ 'hostDirectives': [{
+ directive: CdkMenuItem,
+ outputs: ['cdkMenuItemTriggered: triggered'],
+ }]
+ })
+ class Test {}
+ `,
+ `
+ @Component({
+ selector: 'foo',
+ ['hostDirectives']: [{
+ directive: CdkMenuItem,
+ outputs: ['cdkMenuItemTriggered: triggered'],
+ }]
+ })
+ class Test {}
+ `,
`
@Directive({
selector: 'foo'
diff --git a/packages/eslint-plugin/tests/rules/no-outputs-metadata-property/cases.ts b/packages/eslint-plugin/tests/rules/no-outputs-metadata-property/cases.ts
index daa6aed39..e09dd4ee7 100644
--- a/packages/eslint-plugin/tests/rules/no-outputs-metadata-property/cases.ts
+++ b/packages/eslint-plugin/tests/rules/no-outputs-metadata-property/cases.ts
@@ -45,6 +45,40 @@ export const valid = [
})
class Test {}
`,
+ /**
+ * Renaming outputs when using the directive composition API is not a bad practice
+ * https://angular.io/guide/directive-composition-api
+ */
+ `
+ @Component({
+ selector: 'foo',
+ hostDirectives: [{
+ directive: CdkMenuItem,
+ outputs: ['cdkMenuItemTriggered: triggered'],
+ }]
+ })
+ class Test {}
+ `,
+ `
+ @Component({
+ selector: 'foo',
+ 'hostDirectives': [{
+ directive: CdkMenuItem,
+ outputs: ['cdkMenuItemTriggered: triggered'],
+ }]
+ })
+ class Test {}
+ `,
+ `
+ @Component({
+ selector: 'foo',
+ ['hostDirectives']: [{
+ directive: CdkMenuItem,
+ outputs: ['cdkMenuItemTriggered: triggered'],
+ }]
+ })
+ class Test {}
+ `,
];
export const invalid = [